From 9e625c2b46d765df59c77607d87820d70170f453 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 9 May 2026 08:18:28 +0200 Subject: [PATCH 01/12] =?UTF-8?q?=F0=9F=93=9D=20Retoques=20en=20README's?= =?UTF-8?q?=20y=20documentaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CREDITS.md | 7 ------- README.md | 3 ++- extensions/pagetop-aliner/README.md | 1 - extensions/pagetop-bootsier/README.md | 8 +++++++- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index eaf97333..f61bf867 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -26,13 +26,6 @@ para mostrar un banner de presentación en el terminal con el nombre de la aplic * [starwars.flf](http://www.figlet.org/fontdb_example.cgi?font=starwars.flf) de *Ryan Youck* -# 🎨 CSS - -La extensión `pagetop-bootsier` es un tema que integra [Bootstrap 5.3.8](https://getbootstrap.com/) -para los estilos y componentes de la interfaz. Bootstrap está distribuido bajo licencia -[MIT](https://github.com/twbs/bootstrap/blob/main/LICENSE). - - # 👾 Icono "La Mascota" sonriente es una simpática creación de [Webalys](https://www.iconfinder.com/webalys). diff --git a/README.md b/README.md index 463855f2..8ce2e3f1 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,10 @@ [![Descargas](https://img.shields.io/crates/d/pagetop.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop) [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](https://git.cillero.es/manuelcillero/pagetop#licencia) -
+
+ PageTop reivindica la esencia de la web clásica usando [Rust](https://www.rust-lang.org/es) para la creación de soluciones web SSR (*renderizadas en el servidor*) basadas en HTML, CSS y JavaScript. Ofrece un conjunto de herramientas que los desarrolladores pueden implementar, extender o adaptar diff --git a/extensions/pagetop-aliner/README.md b/extensions/pagetop-aliner/README.md index 09a337eb..7b772591 100644 --- a/extensions/pagetop-aliner/README.md +++ b/extensions/pagetop-aliner/README.md @@ -9,7 +9,6 @@ [![Descargas](https://img.shields.io/crates/d/pagetop-aliner.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-aliner) [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/extensions/pagetop-aliner#licencia) -
## 🧭 Sobre PageTop diff --git a/extensions/pagetop-bootsier/README.md b/extensions/pagetop-bootsier/README.md index b34eeb51..edb0be75 100644 --- a/extensions/pagetop-bootsier/README.md +++ b/extensions/pagetop-bootsier/README.md @@ -9,7 +9,6 @@ [![Descargas](https://img.shields.io/crates/d/pagetop-bootsier.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-bootsier) [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/extensions/pagetop-bootsier#licencia) -
## 🧭 Sobre PageTop @@ -80,6 +79,13 @@ async fn homepage(request: HttpRequest) -> ResultPage { ``` +## 📚 Créditos + +Este *crate* integra la biblioteca de estilos [Bootstrap 5.3.8](https://getbootstrap.com/) para +definir el comportamiento, la apariencia y los componentes de la interfaz. Bootstrap se distribuye +bajo licencia [MIT](https://github.com/twbs/bootstrap/blob/main/LICENSE). + + ## 🚧 Advertencia **PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su From 35883bdcde435965040b0b5e4b6d20d0f38646b2 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 9 May 2026 09:35:59 +0200 Subject: [PATCH 02/12] =?UTF-8?q?=E2=9C=A8=20A=C3=B1ade=20alias=20`cargo?= =?UTF-8?q?=20td`=20y=20aclara=20doc=20de=20pruebas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cargo/config.toml | 1 + README.md | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index d29b0de3..610b7b2e 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,4 @@ [alias] ts = ["test", "--features", "testing"] # cargo ts tw = ["test", "--workspace", "--features", "testing"] # cargo tw +td = ["test", "--doc", "-p"] # cargo td diff --git a/README.md b/README.md index 8ce2e3f1..62de4e2b 100644 --- a/README.md +++ b/README.md @@ -116,16 +116,20 @@ El código se organiza en un *workspace* donde actualmente se incluyen los sigui Para simplificar el flujo de trabajo, el repositorio incluye varios **alias de Cargo** declarados en `.cargo/config.toml`. Basta con ejecutarlos desde la raíz del proyecto: -| Comando | Descripción | -| ------- | ----------- | -| `cargo ts` | Ejecuta los tests de `pagetop` (*unit + integration*) con la *feature* `testing`. | -| `cargo ts --test util` | Lanza sólo las pruebas de integración del módulo `util`. | -| `cargo ts --doc locale` | Lanza las pruebas de la documentación del módulo `locale`. | -| `cargo tw` | Ejecuta los tests de **todos los paquetes** del *workspace*. | +| Comando | Descripción | +| ----------------------- | --------------------------------------------------------------- | +| `cargo ts` | Lanza **todos los tests** de `pagetop` | +| `cargo ts --test util` | Lanza los tests de integración del archivo `tests/util.rs` | +| `cargo ts --doc locale` | Lanza los *doctests* de `pagetop` cuyo *path* contiene `locale` | +| `cargo tw` | Lanza **todos los tests** del *workspace* | +| `cargo td ` | Lanza los *doctests* de un *crate* concreto del *workspace* | > **Nota** -> Estos alias ya compilan con la configuración adecuada. No requieren `--no-default-features`. -> Si quieres **activar** las trazas del registro de eventos entonces usa simplemente `cargo test`. +> * Todos los alias, excepto `cargo td`, aplican la *feature* `testing` para los *crates* que la +> declaren. +> * Cuando lanza **todos los tests** se incluyen las pruebas unitarias, de integración y *doctests*. +> * Los alias suprimen las trazas del registro de eventos. Para activarlas usa directamente +> `cargo test`. ## 🚧 Advertencia From 50abfe3b564828288d5e60d76f29fd205a3bb1a2 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 9 May 2026 10:42:48 +0200 Subject: [PATCH 03/12] =?UTF-8?q?=F0=9F=8C=90=20(aliner):=20Localiza=20nom?= =?UTF-8?q?bre=20y=20descripci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/pagetop-aliner/src/lib.rs | 10 ++++++++++ .../pagetop-aliner/src/locale/en-US/extension.ftl | 2 ++ .../pagetop-aliner/src/locale/es-ES/extension.ftl | 2 ++ 3 files changed, 14 insertions(+) create mode 100644 extensions/pagetop-aliner/src/locale/en-US/extension.ftl create mode 100644 extensions/pagetop-aliner/src/locale/es-ES/extension.ftl diff --git a/extensions/pagetop-aliner/src/lib.rs b/extensions/pagetop-aliner/src/lib.rs index 95f22196..7a29eebc 100644 --- a/extensions/pagetop-aliner/src/lib.rs +++ b/extensions/pagetop-aliner/src/lib.rs @@ -83,6 +83,8 @@ async fn homepage(request: HttpRequest) -> ResultPage { use pagetop::prelude::*; +include_locales!(LOCALES_ALINER); + /// Implementa el tema para usar en pruebas que muestran el esquema de páginas HTML. /// /// Define un tema mínimo útil para: @@ -94,6 +96,14 @@ use pagetop::prelude::*; pub struct Aliner; impl Extension for Aliner { + fn name(&self) -> L10n { + L10n::t("extension_name", &LOCALES_ALINER) + } + + fn description(&self) -> L10n { + L10n::t("extension_description", &LOCALES_ALINER) + } + fn theme(&self) -> Option { Some(&Self) } diff --git a/extensions/pagetop-aliner/src/locale/en-US/extension.ftl b/extensions/pagetop-aliner/src/locale/en-US/extension.ftl new file mode 100644 index 00000000..e4fca26d --- /dev/null +++ b/extensions/pagetop-aliner/src/locale/en-US/extension.ftl @@ -0,0 +1,2 @@ +extension_name = Aliner +extension_description = Minimal theme that schematically shows the HTML page composition. diff --git a/extensions/pagetop-aliner/src/locale/es-ES/extension.ftl b/extensions/pagetop-aliner/src/locale/es-ES/extension.ftl new file mode 100644 index 00000000..5501e15a --- /dev/null +++ b/extensions/pagetop-aliner/src/locale/es-ES/extension.ftl @@ -0,0 +1,2 @@ +extension_name = Aliner +extension_description = Tema mínimo que muestra esquemáticamente la composición de las páginas HTML. \ No newline at end of file From b4284f74f86e410510d7a6eaa241f07195d8169f Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 9 May 2026 10:43:04 +0200 Subject: [PATCH 04/12] =?UTF-8?q?=F0=9F=8C=90=20(bootsier):=20Localiza=20n?= =?UTF-8?q?ombre=20y=20descripci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/pagetop-bootsier/src/lib.rs | 8 ++++++++ .../pagetop-bootsier/src/locale/en-US/extension.ftl | 2 ++ .../pagetop-bootsier/src/locale/es-ES/extension.ftl | 2 ++ 3 files changed, 12 insertions(+) create mode 100644 extensions/pagetop-bootsier/src/locale/en-US/extension.ftl create mode 100644 extensions/pagetop-bootsier/src/locale/es-ES/extension.ftl diff --git a/extensions/pagetop-bootsier/src/lib.rs b/extensions/pagetop-bootsier/src/lib.rs index d562ec09..b07b42d5 100644 --- a/extensions/pagetop-bootsier/src/lib.rs +++ b/extensions/pagetop-bootsier/src/lib.rs @@ -134,6 +134,14 @@ impl Template for BootsierTemplate { pub struct Bootsier; impl Extension for Bootsier { + fn name(&self) -> L10n { + L10n::t("extension_name", &LOCALES_BOOTSIER) + } + + fn description(&self) -> L10n { + L10n::t("extension_description", &LOCALES_BOOTSIER) + } + fn theme(&self) -> Option { Some(&Self) } diff --git a/extensions/pagetop-bootsier/src/locale/en-US/extension.ftl b/extensions/pagetop-bootsier/src/locale/en-US/extension.ftl new file mode 100644 index 00000000..b2820621 --- /dev/null +++ b/extensions/pagetop-bootsier/src/locale/en-US/extension.ftl @@ -0,0 +1,2 @@ +extension_name = Bootsier +extension_description = Bootstrap-based theme with flexible styles and components. diff --git a/extensions/pagetop-bootsier/src/locale/es-ES/extension.ftl b/extensions/pagetop-bootsier/src/locale/es-ES/extension.ftl new file mode 100644 index 00000000..7323cc1b --- /dev/null +++ b/extensions/pagetop-bootsier/src/locale/es-ES/extension.ftl @@ -0,0 +1,2 @@ +extension_name = Bootsier +extension_description = Tema basado en Bootstrap para aplicar su catálogo de estilos y componentes flexibles. From 23d4fd8a80fd344f76b808f524645634d46357fb Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 9 May 2026 13:07:49 +0200 Subject: [PATCH 05/12] =?UTF-8?q?=E2=9C=A8=20(seaorm):=20A=C3=B1ade=20acce?= =?UTF-8?q?so=20a=20bases=20de=20datos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 1823 ++++++++++++++++- Cargo.toml | 2 + README.md | 3 + extensions/pagetop-seaorm/Cargo.toml | 36 + extensions/pagetop-seaorm/LICENSE-APACHE | 201 ++ extensions/pagetop-seaorm/LICENSE-MIT | 21 + extensions/pagetop-seaorm/README.md | 155 ++ extensions/pagetop-seaorm/src/config.rs | 72 + extensions/pagetop-seaorm/src/db.rs | 132 ++ extensions/pagetop-seaorm/src/db/dbconn.rs | 69 + extensions/pagetop-seaorm/src/db/migration.rs | 33 + .../src/db/migration/connection.rs | 148 ++ .../src/db/migration/manager.rs | 167 ++ .../src/db/migration/migrator.rs | 593 ++++++ .../src/db/migration/prelude.rs | 13 + .../pagetop-seaorm/src/db/migration/schema.rs | 608 ++++++ .../src/db/migration/seaql_migrations.rs | 15 + extensions/pagetop-seaorm/src/lib.rs | 29 + .../src/locale/en-US/extension.ftl | 2 + .../src/locale/es-ES/extension.ftl | 2 + tools/changelog.sh | 9 +- tools/release.sh | 2 + 22 files changed, 4051 insertions(+), 84 deletions(-) create mode 100644 extensions/pagetop-seaorm/Cargo.toml create mode 100644 extensions/pagetop-seaorm/LICENSE-APACHE create mode 100644 extensions/pagetop-seaorm/LICENSE-MIT create mode 100644 extensions/pagetop-seaorm/README.md create mode 100644 extensions/pagetop-seaorm/src/config.rs create mode 100644 extensions/pagetop-seaorm/src/db.rs create mode 100644 extensions/pagetop-seaorm/src/db/dbconn.rs create mode 100644 extensions/pagetop-seaorm/src/db/migration.rs create mode 100644 extensions/pagetop-seaorm/src/db/migration/connection.rs create mode 100644 extensions/pagetop-seaorm/src/db/migration/manager.rs create mode 100644 extensions/pagetop-seaorm/src/db/migration/migrator.rs create mode 100644 extensions/pagetop-seaorm/src/db/migration/prelude.rs create mode 100644 extensions/pagetop-seaorm/src/db/migration/schema.rs create mode 100644 extensions/pagetop-seaorm/src/db/migration/seaql_migrations.rs create mode 100644 extensions/pagetop-seaorm/src/lib.rs create mode 100644 extensions/pagetop-seaorm/src/locale/en-US/extension.ftl create mode 100644 extensions/pagetop-seaorm/src/locale/es-ES/extension.ftl diff --git a/Cargo.lock b/Cargo.lock index bf8d11ed..f2541410 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags", + "bitflags 2.11.1", "bytes", "futures-core", "futures-sink", @@ -29,7 +29,7 @@ dependencies = [ "actix-service", "actix-utils", "actix-web", - "bitflags", + "bitflags 2.11.1", "bytes", "derive_more 2.1.1", "futures-core", @@ -53,7 +53,7 @@ dependencies = [ "actix-service", "actix-utils", "base64 0.22.1", - "bitflags", + "bitflags 2.11.1", "brotli", "bytes", "bytestring", @@ -73,7 +73,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rand 0.10.1", - "sha1", + "sha1 0.11.0", "smallvec", "tokio", "tokio-util", @@ -88,7 +88,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -222,7 +222,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -273,6 +273,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -287,6 +288,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + [[package]] name = "alloc-no-stdlib" version = "2.0.4" @@ -373,6 +380,196 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.4.1", + "futures-lite 2.6.1", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-io 2.6.0", + "async-lock 3.4.2", + "blocking", + "futures-lite 2.6.1", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.28", + "slab", + "socket2 0.4.10", + "waker-fn", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.6.1", + "parking", + "polling 3.11.0", + "rustix 1.1.4", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-std" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io 2.6.0", + "async-lock 3.4.2", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite 2.6.1", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.5.0" @@ -385,17 +582,38 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +dependencies = [ + "serde_core", +] [[package]] name = "block-buffer" @@ -415,6 +633,19 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel 2.5.0", + "async-task", + "futures-io", + "futures-lite 2.6.1", + "piper", +] + [[package]] name = "brotli" version = "8.0.2" @@ -475,9 +706,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.61" +version = "1.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" dependencies = [ "find-msvc-tools", "jobserver", @@ -589,6 +820,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7439becb5fafc780b6f4de382b1a7a3e70234afe783854a4702ee8adbb838609" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "config" version = "0.15.22" @@ -601,6 +841,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "const-oid" version = "0.10.2" @@ -640,6 +886,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -664,6 +920,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + [[package]] name = "crc32fast" version = "1.5.0" @@ -701,6 +972,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -736,6 +1016,51 @@ dependencies = [ "cipher", ] +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid 0.9.6", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.5.8" @@ -755,7 +1080,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.117", ] [[package]] @@ -777,7 +1102,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.117", "unicode-xid", ] @@ -788,18 +1113,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", + "const-oid 0.9.6", "crypto-common 0.1.7", "subtle", ] [[package]] name = "digest" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.0", - "const-oid", + "const-oid 0.10.2", "crypto-common 0.2.1", ] @@ -811,7 +1137,34 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "educe" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4bd92664bf78c4d3dba9b7cdafce6fa15b13ed3ed16175218196942e99168a8" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", ] [[package]] @@ -823,6 +1176,26 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -839,6 +1212,53 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fastrand" version = "2.4.1" @@ -902,7 +1322,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54f0d287c53ffd184d04d8677f590f4ac5379785529e5e08b1c8083acdd5c198" dependencies = [ "memchr", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -915,7 +1335,7 @@ dependencies = [ "ignore", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "unic-langid", ] @@ -933,7 +1353,7 @@ dependencies = [ "ignore", "intl-memoizer", "log", - "thiserror", + "thiserror 2.0.18", "unic-langid", ] @@ -943,6 +1363,8 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ + "futures-core", + "futures-sink", "spin", ] @@ -958,6 +1380,21 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -967,12 +1404,104 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + [[package]] name = "futures-core" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand 2.4.1", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "futures-sink" version = "0.3.32" @@ -991,8 +1520,13 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "slab", ] @@ -1054,7 +1588,7 @@ checksum = "c43d815f896a3c730f0d76b8348a1700dc8d8fd6c377e4590d531bdd646574d8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1086,6 +1620,18 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "grass" version = "0.13.4" @@ -1151,9 +1697,27 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "heck" @@ -1161,6 +1725,24 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hkdf" version = "0.12.4" @@ -1179,6 +1761,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "http" version = "0.2.12" @@ -1329,6 +1920,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -1379,7 +1976,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.0", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -1393,6 +1990,17 @@ dependencies = [ "rustversion", ] +[[package]] +name = "inherent" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c727f80bfa4a6c6e2508d2f05b6f4bfce242030bd88ed15ae5331c5b5d30fba7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "inout" version = "0.1.4" @@ -1402,6 +2010,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + [[package]] name = "intl-memoizer" version = "0.5.3" @@ -1421,6 +2038,17 @@ dependencies = [ "unic-langid", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -1445,9 +2073,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.97" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" dependencies = [ "cfg-if", "futures-util", @@ -1455,6 +2083,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -1475,6 +2112,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "leb128fmt" @@ -1488,6 +2128,41 @@ version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "bitflags 2.11.1", + "libc", + "plain", + "redox_syscall 0.7.5", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -1531,6 +2206,9 @@ name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +dependencies = [ + "value-bag", +] [[package]] name = "matchers" @@ -1541,6 +2219,16 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", +] + [[package]] name = "memchr" version = "2.8.0" @@ -1563,6 +2251,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1591,6 +2285,33 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e94e1e6445d314f972ff7395df2de295fe51b71821694f0b0e1e79c4f12c8577" +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -1600,12 +2321,48 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.6", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1613,6 +2370,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1633,6 +2391,82 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openssl" +version = "0.10.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf0b434746ee2832f4f0baf10137e1cabb18cbe6912c69e2e33263c45250f542" +dependencies = [ + "bitflags 2.11.1", + "cfg-if", + "foreign-types", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "158fe5b292746440aa6e7a7e690e55aeb72d41505e2804c23c6973ad0e9c9781" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-float" +version = "3.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ouroboros" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2ba07320d39dfea882faa70554b4bd342a5f273ed59ba7c1c6b4c840492c954" +dependencies = [ + "aliasable", + "ouroboros_macro", + "static_assertions", +] + +[[package]] +name = "ouroboros_macro" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec4c6225c69b4ca778c0aea097321a64c421cf4577b331c61b229267edabb6f8" +dependencies = [ + "heck 0.4.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "pagetop" version = "0.5.0" @@ -1699,7 +2533,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1711,6 +2545,19 @@ dependencies = [ "pastey", ] +[[package]] +name = "pagetop-seaorm" +version = "0.0.4" +dependencies = [ + "async-trait", + "futures", + "pagetop", + "sea-orm", + "sea-schema", + "serde", + "url", +] + [[package]] name = "pagetop-statics" version = "0.1.3" @@ -1723,6 +2570,12 @@ dependencies = [ "path-slash 0.2.1", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.5" @@ -1741,11 +2594,17 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", "windows-link", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pastey" version = "0.2.2" @@ -1779,6 +2638,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -1815,7 +2683,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1829,22 +2697,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.11" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +checksum = "cbf0d9e68100b3a7989b4901972f265cd542e560a3a8a724e1e20322f4d06ce9" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.11" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +checksum = "a990e22f43e84855daf260dded30524ef4a9021cc7541c26540500a50b624389" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1853,12 +2721,86 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand 2.4.1", + "futures-io", +] + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + [[package]] name = "polyval" version = "0.6.2" @@ -1902,7 +2844,53 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -1928,7 +2916,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "version_check", ] @@ -2035,7 +3023,16 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.11.1", +] + +[[package]] +name = "redox_syscall" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4666a1a60d8412eab19d94f6d13dcc9cea0a5ef4fdf6a5db306537413c661b1b" +dependencies = [ + "bitflags 2.11.1", ] [[package]] @@ -2073,6 +3070,26 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid 0.9.6", + "digest 0.10.7", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rustc-hash" version = "2.1.2" @@ -2088,16 +3105,30 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.37.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + [[package]] name = "rustix" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags", + "bitflags 2.11.1", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] @@ -2122,12 +3153,152 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sea-bae" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f694a6ab48f14bc063cfadff30ab551d3c7e46d8f81836c51989d548f44a2a25" +dependencies = [ + "heck 0.4.1", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "sea-orm" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea1fee0cf8528dbe6eda29d5798afc522a63b75e44c5b15721e6e64af9c7cc4b" +dependencies = [ + "async-stream", + "async-trait", + "futures", + "log", + "ouroboros", + "sea-orm-macros", + "sea-query", + "sea-query-binder", + "serde", + "sqlx", + "strum", + "thiserror 1.0.69", + "tracing", + "url", +] + +[[package]] +name = "sea-orm-macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8737b566799ed0444f278d13c300c4c6f1a91782f60ff5825a591852d5502030" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "sea-bae", + "syn 2.0.117", + "unicode-ident", +] + +[[package]] +name = "sea-query" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4fd043b8117af233e221f73e3ea8dfbc8e8c3c928017c474296db45c649105c" +dependencies = [ + "educe", + "inherent", + "ordered-float", + "sea-query-derive", +] + +[[package]] +name = "sea-query-binder" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754965d4aee6145bec25d0898e5c931e6c22859789ce62fd85a42a15ed5a8ce3" +dependencies = [ + "sea-query", + "sqlx", +] + +[[package]] +name = "sea-query-derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae0cbad6ab996955664982739354128c58d16e126114fe88c2a493642502aab" +dependencies = [ + "darling", + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.117", + "thiserror 2.0.18", +] + +[[package]] +name = "sea-schema" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad52149fc81836ea7424c3425d8f6ed8ad448dd16d2e4f6a3907ba46f3f2fd78" +dependencies = [ + "futures", + "sea-query", + "sea-schema-derive", +] + +[[package]] +name = "sea-schema-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "debdc8729c37fdbf88472f97fd470393089f997a909e535ff67c544d18cfccf0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.11.1", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "self_cell" version = "1.2.2" @@ -2167,7 +3338,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2204,6 +3375,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + [[package]] name = "sha1" version = "0.11.0" @@ -2212,7 +3394,7 @@ checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "digest 0.11.2", + "digest 0.11.3", ] [[package]] @@ -2251,6 +3433,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + [[package]] name = "simd-adler32" version = "0.3.9" @@ -2259,9 +3451,9 @@ checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "siphasher" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" [[package]] name = "slab" @@ -2275,6 +3467,16 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "socket2" version = "0.5.10" @@ -2304,18 +3506,256 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" +dependencies = [ + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" +dependencies = [ + "ahash", + "async-io 1.13.0", + "async-std", + "atoi", + "byteorder", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener 2.5.3", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashlink", + "hex", + "indexmap", + "log", + "memchr", + "native-tls", + "once_cell", + "paste", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror 1.0.69", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" +dependencies = [ + "async-std", + "dotenvy", + "either", + "heck 0.4.1", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 1.0.109", + "tempfile", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" +dependencies = [ + "atoi", + "base64 0.21.7", + "bitflags 2.11.1", + "byteorder", + "bytes", + "crc", + "digest 0.10.7", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.6", + "rsa", + "serde", + "sha1 0.10.6", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 1.0.69", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" +dependencies = [ + "atoi", + "base64 0.21.7", + "bitflags 2.11.1", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.6", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 1.0.69", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "tracing", + "url", + "urlencoding", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" + [[package]] name = "substring" version = "1.4.5" @@ -2337,6 +3777,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.117" @@ -2356,7 +3807,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2365,10 +3816,10 @@ version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ - "fastrand", + "fastrand 2.4.1", "getrandom 0.4.2", "once_cell", - "rustix", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -2378,17 +3829,37 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874" dependencies = [ - "rustix", + "rustix 1.1.4", "windows-sys 0.61.2", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -2399,7 +3870,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2454,10 +3925,25 @@ dependencies = [ ] [[package]] -name = "tokio" -version = "1.52.1" +name = "tinyvec" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -2546,7 +4032,7 @@ checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" dependencies = [ "crossbeam-channel", "symlink", - "thiserror", + "thiserror 2.0.18", "time", "tracing-subscriber", ] @@ -2559,7 +4045,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2668,7 +4154,7 @@ checksum = "a1249a628de3ad34b821ecb1001355bca3940bcb2f88558f1a8bd82e977f75b5" dependencies = [ "proc-macro-hack", "quote", - "syn", + "syn 2.0.117", "unic-langid-impl", ] @@ -2678,12 +4164,33 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + [[package]] name = "unicode-segmentation" version = "1.13.2" @@ -2696,6 +4203,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + [[package]] name = "universal-hash" version = "0.5.1" @@ -2718,6 +4231,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -2753,12 +4272,30 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "value-bag" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + [[package]] name = "walkdir" version = "2.5.0" @@ -2794,10 +4331,16 @@ dependencies = [ ] [[package]] -name = "wasm-bindgen" -version = "0.2.120" +name = "wasite" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" dependencies = [ "cfg-if", "once_cell", @@ -2807,10 +4350,20 @@ dependencies = [ ] [[package]] -name = "wasm-bindgen-macro" -version = "0.2.120" +name = "wasm-bindgen-futures" +version = "0.4.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" +checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.121" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2818,22 +4371,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.120" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.120" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" dependencies = [ "unicode-ident", ] @@ -2866,12 +4419,38 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags", + "bitflags 2.11.1", "hashbrown 0.15.5", "indexmap", "semver", ] +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.11" @@ -2881,6 +4460,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.62.2" @@ -2902,7 +4487,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2913,7 +4498,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2940,13 +4525,22 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -2958,34 +4552,67 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -2998,24 +4625,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -3053,7 +4704,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ "anyhow", - "heck", + "heck 0.5.0", "wit-parser", ] @@ -3064,10 +4715,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", - "heck", + "heck 0.5.0", "indexmap", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -3083,7 +4734,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -3095,7 +4746,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags", + "bitflags 2.11.1", "indexmap", "log", "serde", @@ -3150,7 +4801,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -3171,7 +4822,7 @@ checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3191,10 +4842,16 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + [[package]] name = "zerotrie" version = "0.2.4" @@ -3226,7 +4883,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d809c122..48bc600c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,6 +69,7 @@ members = [ # Extensions "extensions/pagetop-aliner", "extensions/pagetop-bootsier", + "extensions/pagetop-seaorm", ] [workspace.package] @@ -88,5 +89,6 @@ pagetop-statics = { version = "0.1", path = "helpers/pagetop-statics" } # Extensions pagetop-aliner = { version = "0.1", path = "extensions/pagetop-aliner" } pagetop-bootsier = { version = "0.1", path = "extensions/pagetop-bootsier" } +pagetop-seaorm = { version = "0.0", path = "extensions/pagetop-seaorm" } # PageTop pagetop = { version = "0.5", path = "." } diff --git a/README.md b/README.md index 62de4e2b..604c4b3c 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,9 @@ El código se organiza en un *workspace* donde actualmente se incluyen los sigui tema basado en [Bootstrap](https://getbootstrap.com) para integrar su catálogo de estilos y componentes flexibles. + * **[pagetop-seaorm](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/extensions/pagetop-seaorm)**, + integra [SeaORM](https://www.sea-ql.org/SeaORM) para acceder a bases de datos relacionales. + ## 🧪 Pruebas diff --git a/extensions/pagetop-seaorm/Cargo.toml b/extensions/pagetop-seaorm/Cargo.toml new file mode 100644 index 00000000..404bb84b --- /dev/null +++ b/extensions/pagetop-seaorm/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "pagetop-seaorm" +version = "0.0.4" +edition = "2021" + +description = """ + Proporciona a PageTop acceso basado en SeaORM a bases de datos relacionales. +""" +categories = ["database", "development-tools", "asynchronous"] +keywords = ["pagetop", "database", "sql", "orm", "ssr"] + +repository.workspace = true +homepage.workspace = true +license.workspace = true +authors.workspace = true + +[features] +mysql = ["sea-orm/sqlx-mysql"] +postgres = ["sea-orm/sqlx-postgres"] +sqlite = ["sea-orm/sqlx-sqlite"] + +[dependencies] +pagetop.workspace = true +serde.workspace = true + +async-trait = "0.1" +futures = "0.3" +url = "2.5" + +[dependencies.sea-orm] +version = "~1.0" +features = ["debug-print", "macros", "runtime-async-std-native-tls"] +default-features = false + +[dependencies.sea-schema] +version = "~0.15" diff --git a/extensions/pagetop-seaorm/LICENSE-APACHE b/extensions/pagetop-seaorm/LICENSE-APACHE new file mode 100644 index 00000000..263ddac1 --- /dev/null +++ b/extensions/pagetop-seaorm/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2022 Manuel Cillero + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/extensions/pagetop-seaorm/LICENSE-MIT b/extensions/pagetop-seaorm/LICENSE-MIT new file mode 100644 index 00000000..cd8af3d6 --- /dev/null +++ b/extensions/pagetop-seaorm/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Manuel Cillero + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/extensions/pagetop-seaorm/README.md b/extensions/pagetop-seaorm/README.md new file mode 100644 index 00000000..aa76275a --- /dev/null +++ b/extensions/pagetop-seaorm/README.md @@ -0,0 +1,155 @@ +
+ +

PageTop SeaORM

+ +

Proporciona a PageTop acceso basado en SeaORM a bases de datos relacionales.

+ +[![Doc API](https://img.shields.io/docsrs/pagetop-seaorm?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-seaorm) +[![Crates.io](https://img.shields.io/crates/v/pagetop-seaorm.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-seaorm) +[![Descargas](https://img.shields.io/crates/d/pagetop-seaorm.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-seaorm) +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/extensions/pagetop-seaorm#licencia) + +
+ +## 🧭 Sobre PageTop + +[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web +clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y +configurables, basadas en HTML, CSS y JavaScript. + + +## ⚡️ Guía rápida + +**Añade la dependencia** a tu `Cargo.toml` activando el motor de base de datos que necesites: + +```toml +[dependencies] +pagetop-seaorm = { version = "...", features = ["sqlite"] } +``` + +Las *features* disponibles son `mysql`, `postgres` y `sqlite`. + +**Configura la conexión** en el archivo de configuración de la aplicación: + +```toml +[database] +db_type = "sqlite" +db_name = "my_app.db" +max_pool_size = 5 +``` + +Para MySQL o PostgreSQL añade también `db_user`, `db_pass`, `db_host` y `db_port`. + +**Declara la extensión** en tu aplicación o en la extensión que la requiera: + +```rust,no_run +use pagetop::prelude::*; + +struct MyApp; + +impl Extension for MyApp { + fn dependencies(&self) -> Vec { + vec![ + &pagetop_seaorm::SeaORM, + ] + } + + fn initialize(&self) { + install_migrations!(m20240101_000001_create_users_table); + } +} + +#[pagetop::main] +async fn main() -> std::io::Result<()> { + Application::prepare(&MyApp).run()?.await +} +``` + +**Escribe las migraciones** usando la API de SeaORM: + +```rust,no_run +use pagetop_seaorm::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + table_auto(Users::Table) + .col(pk_auto(Users::Id)) + .col(string_uniq(Users::Email)) + .to_owned(), + ) + .await + } +} + +#[derive(DeriveIden)] +enum Users { + Table, + Id, + Email, +} +``` + + +## 📚 Créditos + +Este *crate* se apoya en bibliotecas del ecosistema [SeaQL](https://github.com/SeaQL) como: + +* [SeaORM](https://www.sea-ql.org/SeaORM), ORM asíncrono que usa internamente + [SQLx](https://github.com/launchbadge/sqlx) para el acceso y la ejecución de consultas a la base + de datos. + +* [SeaQuery](https://github.com/SeaQL/sea-query), generador de consultas SQL sobre el que se + construye el motor de migraciones y los *helpers* de esquema. + +* [sea-schema](https://github.com/SeaQL/sea-schema), librería de introspección de esquemas SQL, + usada por el módulo de migraciones para interrogar la estructura real de la base de datos (tablas, + columnas, índices y claves externas). + +También incorpora código adaptado de las siguientes fuentes: + +* [**sea-orm-migration (v1.0.0)**](https://github.com/SeaQL/sea-orm/tree/1.0.0/sea-orm-migration): + El módulo de migraciones (`src/db/migration/`) es una versión personalizada de + [sea-orm-migration](https://crates.io/crates/sea-orm-migration). Se integra directamente en lugar + de usarlo como dependencia porque su paradigma de CLI no es compatible con el ciclo de vida de las + extensiones de PageTop, donde las migraciones deben ejecutarse durante la inicialización de cada + extensión. Los ficheros adaptados del original son: + + | Original en `sea-orm-migration` | Observaciones | + |---------------------------------|-----------------------------------------| + | `lib.rs` | Excluye módulos y exportaciones del CLI | + | `connection.rs` | Integración completa | + | `manager.rs` | Integración completa | + | `migrator.rs` | Omite la gestión de errores del CLI | + | `prelude.rs` | Excluye exportaciones del CLI | + | `seaql_migrations.rs` | Integración completa | + +* [**loco-rs/loco**](https://github.com/loco-rs/loco/blob/master/src/schema.rs): El módulo + `src/db/migration/schema.rs`, con funciones de ayuda para definir columnas de tablas de forma + ergonómica, está adaptado del fichero `src/schema.rs` del proyecto [Loco](https://loco.rs/). + + +## 🚧 Advertencia + +**PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su +ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos +hasta que se libere la versión **1.0.0**. + + +## 📜 Licencia + +El código está disponible bajo una doble licencia: + + * **Licencia MIT** + ([LICENSE-MIT](LICENSE-MIT) o también https://opensource.org/licenses/MIT) + + * **Licencia Apache, Versión 2.0** + ([LICENSE-APACHE](LICENSE-APACHE) o también https://www.apache.org/licenses/LICENSE-2.0) + +Puedes elegir la licencia que prefieras. Este enfoque de doble licencia es el estándar de facto en +el ecosistema Rust. diff --git a/extensions/pagetop-seaorm/src/config.rs b/extensions/pagetop-seaorm/src/config.rs new file mode 100644 index 00000000..5c706659 --- /dev/null +++ b/extensions/pagetop-seaorm/src/config.rs @@ -0,0 +1,72 @@ +//! Opciones de configuración de la extensión. +//! +//! Ejemplo: +//! +//! ```toml +//! [database] +//! db_type = "mysql" +//! db_name = "db" +//! db_user = "user" +//! db_pass = "password" +//! db_host = "localhost" +//! db_port = 3306 +//! max_pool_size = 5 +//! ``` +//! +//! Uso: +//! +//! ```rust +//! use pagetop_seaorm::config; +//! +//! assert_eq!(config::SETTINGS.database.db_host, "localhost"); +//! ``` +//! +//! Consulta [`pagetop::config`] para ver cómo PageTop lee los archivos de configuración y aplica +//! los valores a los ajustes. + +use pagetop::prelude::*; + +use serde::Deserialize; + +include_config!(SETTINGS: Settings => [ + // [database] + "database.db_type" => "", + "database.db_name" => "", + "database.db_user" => "", + "database.db_pass" => "", + "database.db_host" => "localhost", + "database.db_port" => 0, + "database.max_pool_size" => 5, +]); + +#[derive(Debug, Deserialize)] +/// Tipos para la sección [`[database]`](Database) de [`SETTINGS`]. +pub struct Settings { + pub database: Database, +} + +#[derive(Debug, Deserialize)] +/// Sección `[database]` de la configuración. Forma parte de [`Settings`]. +pub struct Database { + /// Tipo de base de datos: *"mysql"*, *"postgres"* ó *"sqlite"*. + /// Por defecto: *""*. + pub db_type: String, + /// Nombre (para mysql/postgres) o referencia (para sqlite) de la base de datos. + /// Por defecto: *""*. + pub db_name: String, + /// Usuario de conexión a la base de datos (para mysql/postgres). + /// Por defecto: *""*. + pub db_user: String, + /// Contraseña para la conexión a la base de datos (para mysql/postgres). + /// Por defecto: *""*. + pub db_pass: String, + /// Servidor de conexión a la base de datos (para mysql/postgres). + /// Por defecto: *"localhost"*. + pub db_host: String, + /// Puerto de conexión a la base de datos, normalmente 3306 (para mysql) ó 5432 (para postgres). + /// Por defecto: *0*. + pub db_port: u16, + /// Número máximo de conexiones habilitadas. + /// Por defecto: *5*. + pub max_pool_size: u32, +} diff --git a/extensions/pagetop-seaorm/src/db.rs b/extensions/pagetop-seaorm/src/db.rs new file mode 100644 index 00000000..70b8e57c --- /dev/null +++ b/extensions/pagetop-seaorm/src/db.rs @@ -0,0 +1,132 @@ +use pagetop::core::TypeInfo; +use pagetop::trace; + +pub use url::Url as DbUri; + +pub use sea_orm::error::{DbErr, RuntimeErr}; +pub use sea_orm::{DatabaseConnection as DbConn, ExecResult, QueryResult}; + +use sea_orm::{ConnectionTrait, DatabaseBackend, Statement}; + +mod dbconn; +pub(crate) use dbconn::{run_now, DBCONN}; + +// The migration module is a customized version of the sea_orm_migration module (v1.0.0) +// https://github.com/SeaQL/sea-orm/tree/1.0.0/sea-orm-migration to avoid errors caused by the +// package paradigm of PageTop. Files integrated from original: +// +// lib.rs => db/migration.rs . . . . . . . . . . . . . . (excluding some modules and exports) +// connection.rs => db/migration/connection.rs . . . . . . . . . . . . . . (full integration) +// manager.rs => db/migration/manager.rs . . . . . . . . . . . . . . . . . (full integration) +// migrator.rs => db/migration/migrator.rs . . . . . . . . . . . .(omitting error management) +// prelude.rs => db/migration/prelude.rs . . . . . . . . . . . . . . . . . . . (avoiding CLI) +// seaql_migrations.rs => db/migration/seaql_migrations.rs . . . . . . . . (full integration) +// +mod migration; +pub use migration::prelude::*; +pub use migration::schema::*; + +pub async fn query(stmt: &mut Q) -> Result, DbErr> { + let dbconn = &*DBCONN; + let dbbackend = dbconn.get_database_backend(); + dbconn + .query_all(Statement::from_string( + dbbackend, + match dbbackend { + DatabaseBackend::MySql => stmt.to_string(MysqlQueryBuilder), + DatabaseBackend::Postgres => stmt.to_string(PostgresQueryBuilder), + DatabaseBackend::Sqlite => stmt.to_string(SqliteQueryBuilder), + }, + )) + .await +} + +pub async fn exec(stmt: &mut Q) -> Result, DbErr> { + let dbconn = &*DBCONN; + let dbbackend = dbconn.get_database_backend(); + dbconn + .query_one(Statement::from_string( + dbbackend, + match dbbackend { + DatabaseBackend::MySql => stmt.to_string(MysqlQueryBuilder), + DatabaseBackend::Postgres => stmt.to_string(PostgresQueryBuilder), + DatabaseBackend::Sqlite => stmt.to_string(SqliteQueryBuilder), + }, + )) + .await +} + +pub async fn exec_raw(stmt: String) -> Result { + let dbconn = &*DBCONN; + let dbbackend = dbconn.get_database_backend(); + dbconn + .execute(Statement::from_string(dbbackend, stmt)) + .await +} + +pub trait MigratorBase { + fn run_up(); + + fn run_down(); +} + +#[rustfmt::skip] +impl MigratorBase for M { + fn run_up() { + if let Err(e) = run_now(Self::up(SchemaManagerConnection::Connection(&DBCONN), None)) { + trace::error!("Migration upgrade failed ({})", e); + }; + } + + fn run_down() { + if let Err(e) = run_now(Self::down(SchemaManagerConnection::Connection(&DBCONN), None)) { + trace::error!("Migration downgrade failed ({})", e); + }; + } +} + +impl MigrationName for M { + fn name(&self) -> &str { + TypeInfo::NameTo(-2).of::() + } +} + +pub type MigrationItem = Box; + +#[macro_export] +macro_rules! install_migrations { + ( $($migration_module:ident),+ $(,)? ) => {{ + use $crate::db::{MigrationItem, MigratorBase, MigratorTrait}; + + struct Migrator; + impl MigratorTrait for Migrator { + fn migrations() -> Vec { + let mut m = Vec::::new(); + $( + m.push(Box::new(migration::$migration_module::Migration)); + )* + m + } + } + Migrator::run_up(); + }}; +} + +#[macro_export] +macro_rules! uninstall_migrations { + ( $($migration_module:ident),+ $(,)? ) => {{ + use $crate::db::{MigrationItem, MigratorBase, MigratorTrait}; + + struct Migrator; + impl MigratorTrait for Migrator { + fn migrations() -> Vec { + let mut m = Vec::::new(); + $( + m.push(Box::new(migration::$migration_module::Migration)); + )* + m + } + } + Migrator::run_down(); + }}; +} diff --git a/extensions/pagetop-seaorm/src/db/dbconn.rs b/extensions/pagetop-seaorm/src/db/dbconn.rs new file mode 100644 index 00000000..bd227956 --- /dev/null +++ b/extensions/pagetop-seaorm/src/db/dbconn.rs @@ -0,0 +1,69 @@ +use pagetop::trace; + +use crate::config; +use crate::db::{DbConn, DbUri}; + +use std::sync::LazyLock; + +use sea_orm::{ConnectOptions, Database}; + +pub use futures::executor::block_on as run_now; + +pub static DBCONN: LazyLock = LazyLock::new(|| { + trace::info!( + "Connecting to database \"{}\" using a pool of {} connections", + &config::SETTINGS.database.db_name, + &config::SETTINGS.database.max_pool_size + ); + + let db_uri = match config::SETTINGS.database.db_type.as_str() { + "mysql" | "postgres" => { + let mut tmp_uri = DbUri::parse( + format!( + "{}://{}/{}", + &config::SETTINGS.database.db_type, + &config::SETTINGS.database.db_host, + &config::SETTINGS.database.db_name + ) + .as_str(), + ) + .unwrap(); + tmp_uri + .set_username(config::SETTINGS.database.db_user.as_str()) + .unwrap(); + // https://github.com/launchbadge/sqlx/issues/1624 + tmp_uri + .set_password(Some(config::SETTINGS.database.db_pass.as_str())) + .unwrap(); + if config::SETTINGS.database.db_port != 0 { + tmp_uri + .set_port(Some(config::SETTINGS.database.db_port)) + .unwrap(); + } + tmp_uri + } + "sqlite" => DbUri::parse( + format!( + "{}://{}", + &config::SETTINGS.database.db_type, + &config::SETTINGS.database.db_name + ) + .as_str(), + ) + .unwrap(), + _ => { + trace::error!( + "Unrecognized database type \"{}\"", + &config::SETTINGS.database.db_type + ); + DbUri::parse("").unwrap() + } + }; + + run_now(Database::connect::({ + let mut db_opt = ConnectOptions::new(db_uri.to_string()); + db_opt.max_connections(config::SETTINGS.database.max_pool_size); + db_opt + })) + .unwrap_or_else(|_| panic!("Failed to connect to database")) +}); diff --git a/extensions/pagetop-seaorm/src/db/migration.rs b/extensions/pagetop-seaorm/src/db/migration.rs new file mode 100644 index 00000000..29314bf6 --- /dev/null +++ b/extensions/pagetop-seaorm/src/db/migration.rs @@ -0,0 +1,33 @@ +//pub mod cli; +pub mod connection; +pub mod manager; +pub mod migrator; +pub mod prelude; +pub mod schema; +pub mod seaql_migrations; +//pub mod util; + +pub use connection::*; +pub use manager::*; +//pub use migrator::*; + +pub use async_trait; +//pub use sea_orm; +//pub use sea_orm::sea_query; +use sea_orm::DbErr; + +pub trait MigrationName { + fn name(&self) -> &str; +} + +/// The migration definition +#[async_trait::async_trait] +pub trait MigrationTrait: MigrationName + Send + Sync { + /// Define actions to perform when applying the migration + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr>; + + /// Define actions to perform when rolling back the migration + async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { + Err(DbErr::Migration("We Don't Do That Here".to_owned())) + } +} diff --git a/extensions/pagetop-seaorm/src/db/migration/connection.rs b/extensions/pagetop-seaorm/src/db/migration/connection.rs new file mode 100644 index 00000000..116185e4 --- /dev/null +++ b/extensions/pagetop-seaorm/src/db/migration/connection.rs @@ -0,0 +1,148 @@ +use futures::Future; +use sea_orm::{ + AccessMode, ConnectionTrait, DatabaseConnection, DatabaseTransaction, DbBackend, DbErr, + ExecResult, IsolationLevel, QueryResult, Statement, TransactionError, TransactionTrait, +}; +use std::pin::Pin; + +pub enum SchemaManagerConnection<'c> { + Connection(&'c DatabaseConnection), + Transaction(&'c DatabaseTransaction), +} + +#[async_trait::async_trait] +impl<'c> ConnectionTrait for SchemaManagerConnection<'c> { + fn get_database_backend(&self) -> DbBackend { + match self { + SchemaManagerConnection::Connection(conn) => conn.get_database_backend(), + SchemaManagerConnection::Transaction(trans) => trans.get_database_backend(), + } + } + + async fn execute(&self, stmt: Statement) -> Result { + match self { + SchemaManagerConnection::Connection(conn) => conn.execute(stmt).await, + SchemaManagerConnection::Transaction(trans) => trans.execute(stmt).await, + } + } + + async fn execute_unprepared(&self, sql: &str) -> Result { + match self { + SchemaManagerConnection::Connection(conn) => conn.execute_unprepared(sql).await, + SchemaManagerConnection::Transaction(trans) => trans.execute_unprepared(sql).await, + } + } + + async fn query_one(&self, stmt: Statement) -> Result, DbErr> { + match self { + SchemaManagerConnection::Connection(conn) => conn.query_one(stmt).await, + SchemaManagerConnection::Transaction(trans) => trans.query_one(stmt).await, + } + } + + async fn query_all(&self, stmt: Statement) -> Result, DbErr> { + match self { + SchemaManagerConnection::Connection(conn) => conn.query_all(stmt).await, + SchemaManagerConnection::Transaction(trans) => trans.query_all(stmt).await, + } + } + + fn is_mock_connection(&self) -> bool { + match self { + SchemaManagerConnection::Connection(conn) => conn.is_mock_connection(), + SchemaManagerConnection::Transaction(trans) => trans.is_mock_connection(), + } + } +} + +#[async_trait::async_trait] +impl<'c> TransactionTrait for SchemaManagerConnection<'c> { + async fn begin(&self) -> Result { + match self { + SchemaManagerConnection::Connection(conn) => conn.begin().await, + SchemaManagerConnection::Transaction(trans) => trans.begin().await, + } + } + + async fn begin_with_config( + &self, + isolation_level: Option, + access_mode: Option, + ) -> Result { + match self { + SchemaManagerConnection::Connection(conn) => { + conn.begin_with_config(isolation_level, access_mode).await + } + SchemaManagerConnection::Transaction(trans) => { + trans.begin_with_config(isolation_level, access_mode).await + } + } + } + + async fn transaction(&self, callback: F) -> Result> + where + F: for<'a> FnOnce( + &'a DatabaseTransaction, + ) -> Pin> + Send + 'a>> + + Send, + T: Send, + E: std::error::Error + Send, + { + match self { + SchemaManagerConnection::Connection(conn) => conn.transaction(callback).await, + SchemaManagerConnection::Transaction(trans) => trans.transaction(callback).await, + } + } + + async fn transaction_with_config( + &self, + callback: F, + isolation_level: Option, + access_mode: Option, + ) -> Result> + where + F: for<'a> FnOnce( + &'a DatabaseTransaction, + ) -> Pin> + Send + 'a>> + + Send, + T: Send, + E: std::error::Error + Send, + { + match self { + SchemaManagerConnection::Connection(conn) => { + conn.transaction_with_config(callback, isolation_level, access_mode) + .await + } + SchemaManagerConnection::Transaction(trans) => { + trans + .transaction_with_config(callback, isolation_level, access_mode) + .await + } + } + } +} + +pub trait IntoSchemaManagerConnection<'c>: Send +where + Self: 'c, +{ + fn into_schema_manager_connection(self) -> SchemaManagerConnection<'c>; +} + +impl<'c> IntoSchemaManagerConnection<'c> for SchemaManagerConnection<'c> { + fn into_schema_manager_connection(self) -> SchemaManagerConnection<'c> { + self + } +} + +impl<'c> IntoSchemaManagerConnection<'c> for &'c DatabaseConnection { + fn into_schema_manager_connection(self) -> SchemaManagerConnection<'c> { + SchemaManagerConnection::Connection(self) + } +} + +impl<'c> IntoSchemaManagerConnection<'c> for &'c DatabaseTransaction { + fn into_schema_manager_connection(self) -> SchemaManagerConnection<'c> { + SchemaManagerConnection::Transaction(self) + } +} diff --git a/extensions/pagetop-seaorm/src/db/migration/manager.rs b/extensions/pagetop-seaorm/src/db/migration/manager.rs new file mode 100644 index 00000000..d1cc3b6a --- /dev/null +++ b/extensions/pagetop-seaorm/src/db/migration/manager.rs @@ -0,0 +1,167 @@ +use super::{IntoSchemaManagerConnection, SchemaManagerConnection}; +use sea_orm::sea_query::{ + extension::postgres::{TypeAlterStatement, TypeCreateStatement, TypeDropStatement}, + ForeignKeyCreateStatement, ForeignKeyDropStatement, IndexCreateStatement, IndexDropStatement, + TableAlterStatement, TableCreateStatement, TableDropStatement, TableRenameStatement, + TableTruncateStatement, +}; +use sea_orm::{ConnectionTrait, DbBackend, DbErr, StatementBuilder}; +use sea_schema::{mysql::MySql, postgres::Postgres, probe::SchemaProbe, sqlite::Sqlite}; + +/// Helper struct for writing migration scripts in migration file +pub struct SchemaManager<'c> { + conn: SchemaManagerConnection<'c>, +} + +impl<'c> SchemaManager<'c> { + pub fn new(conn: T) -> Self + where + T: IntoSchemaManagerConnection<'c>, + { + Self { + conn: conn.into_schema_manager_connection(), + } + } + + pub async fn exec_stmt(&self, stmt: S) -> Result<(), DbErr> + where + S: StatementBuilder, + { + let builder = self.conn.get_database_backend(); + self.conn.execute(builder.build(&stmt)).await.map(|_| ()) + } + + pub fn get_database_backend(&self) -> DbBackend { + self.conn.get_database_backend() + } + + pub fn get_connection(&self) -> &SchemaManagerConnection<'c> { + &self.conn + } +} + +/// Schema Creation +impl<'c> SchemaManager<'c> { + pub async fn create_table(&self, stmt: TableCreateStatement) -> Result<(), DbErr> { + self.exec_stmt(stmt).await + } + + pub async fn create_index(&self, stmt: IndexCreateStatement) -> Result<(), DbErr> { + self.exec_stmt(stmt).await + } + + pub async fn create_foreign_key(&self, stmt: ForeignKeyCreateStatement) -> Result<(), DbErr> { + self.exec_stmt(stmt).await + } + + pub async fn create_type(&self, stmt: TypeCreateStatement) -> Result<(), DbErr> { + self.exec_stmt(stmt).await + } +} + +/// Schema Mutation +impl<'c> SchemaManager<'c> { + pub async fn alter_table(&self, stmt: TableAlterStatement) -> Result<(), DbErr> { + self.exec_stmt(stmt).await + } + + pub async fn drop_table(&self, stmt: TableDropStatement) -> Result<(), DbErr> { + self.exec_stmt(stmt).await + } + + pub async fn rename_table(&self, stmt: TableRenameStatement) -> Result<(), DbErr> { + self.exec_stmt(stmt).await + } + + pub async fn truncate_table(&self, stmt: TableTruncateStatement) -> Result<(), DbErr> { + self.exec_stmt(stmt).await + } + + pub async fn drop_index(&self, stmt: IndexDropStatement) -> Result<(), DbErr> { + self.exec_stmt(stmt).await + } + + pub async fn drop_foreign_key(&self, stmt: ForeignKeyDropStatement) -> Result<(), DbErr> { + self.exec_stmt(stmt).await + } + + pub async fn alter_type(&self, stmt: TypeAlterStatement) -> Result<(), DbErr> { + self.exec_stmt(stmt).await + } + + pub async fn drop_type(&self, stmt: TypeDropStatement) -> Result<(), DbErr> { + self.exec_stmt(stmt).await + } +} + +/// Schema Inspection. +impl<'c> SchemaManager<'c> { + pub async fn has_table(&self, table: T) -> Result + where + T: AsRef, + { + has_table(&self.conn, table).await + } + + pub async fn has_column(&self, table: T, column: C) -> Result + where + T: AsRef, + C: AsRef, + { + let stmt = match self.conn.get_database_backend() { + DbBackend::MySql => MySql.has_column(table, column), + DbBackend::Postgres => Postgres.has_column(table, column), + DbBackend::Sqlite => Sqlite.has_column(table, column), + }; + + let builder = self.conn.get_database_backend(); + let res = self + .conn + .query_one(builder.build(&stmt)) + .await? + .ok_or_else(|| DbErr::Custom("Failed to check column exists".to_owned()))?; + + res.try_get("", "has_column") + } + + pub async fn has_index(&self, table: T, index: I) -> Result + where + T: AsRef, + I: AsRef, + { + let stmt = match self.conn.get_database_backend() { + DbBackend::MySql => MySql.has_index(table, index), + DbBackend::Postgres => Postgres.has_index(table, index), + DbBackend::Sqlite => Sqlite.has_index(table, index), + }; + + let builder = self.conn.get_database_backend(); + let res = self + .conn + .query_one(builder.build(&stmt)) + .await? + .ok_or_else(|| DbErr::Custom("Failed to check index exists".to_owned()))?; + + res.try_get("", "has_index") + } +} + +pub(crate) async fn has_table(conn: &C, table: T) -> Result +where + C: ConnectionTrait, + T: AsRef, +{ + let stmt = match conn.get_database_backend() { + DbBackend::MySql => MySql.has_table(table), + DbBackend::Postgres => Postgres.has_table(table), + DbBackend::Sqlite => Sqlite.has_table(table), + }; + + let builder = conn.get_database_backend(); + let res = conn + .query_one(builder.build(&stmt)) + .await? + .ok_or_else(|| DbErr::Custom("Failed to check table exists".to_owned()))?; + + res.try_get("", "has_table") +} diff --git a/extensions/pagetop-seaorm/src/db/migration/migrator.rs b/extensions/pagetop-seaorm/src/db/migration/migrator.rs new file mode 100644 index 00000000..06611412 --- /dev/null +++ b/extensions/pagetop-seaorm/src/db/migration/migrator.rs @@ -0,0 +1,593 @@ +use futures::Future; +use std::collections::HashSet; +use std::fmt::Display; +use std::pin::Pin; +use std::time::SystemTime; + +use pagetop::trace::info; + +use sea_orm::sea_query::{ + self, extension::postgres::Type, Alias, Expr, ForeignKey, IntoIden, JoinType, Order, Query, + SelectStatement, SimpleExpr, Table, +}; +use sea_orm::{ + ActiveModelTrait, ActiveValue, Condition, ConnectionTrait, DbBackend, DbErr, DeriveIden, + DynIden, EntityTrait, FromQueryResult, Iterable, QueryFilter, Schema, Statement, + TransactionTrait, +}; +use sea_schema::{mysql::MySql, postgres::Postgres, probe::SchemaProbe, sqlite::Sqlite}; + +use super::{seaql_migrations, IntoSchemaManagerConnection, MigrationTrait, SchemaManager}; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +/// Status of migration +pub enum MigrationStatus { + /// Not yet applied + Pending, + /// Applied + Applied, +} + +impl Display for MigrationStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let status = match self { + MigrationStatus::Pending => "Pending", + MigrationStatus::Applied => "Applied", + }; + write!(f, "{status}") + } +} + +pub struct Migration { + migration: Box, + status: MigrationStatus, +} + +impl Migration { + /// Get migration name from MigrationName trait implementation + pub fn name(&self) -> &str { + self.migration.name() + } + + /// Get migration status + pub fn status(&self) -> MigrationStatus { + self.status + } +} + +/// Performing migrations on a database +#[async_trait::async_trait] +pub trait MigratorTrait: Send { + /// Vector of migrations in time sequence + fn migrations() -> Vec>; + + /// Name of the migration table, it is `seaql_migrations` by default + fn migration_table_name() -> DynIden { + seaql_migrations::Entity.into_iden() + } + + /// Get list of migrations wrapped in `Migration` struct + fn get_migration_files() -> Vec { + Self::migrations() + .into_iter() + .map(|migration| Migration { + migration, + status: MigrationStatus::Pending, + }) + .collect() + } + + /// Get list of applied migrations from database + async fn get_migration_models(db: &C) -> Result, DbErr> + where + C: ConnectionTrait, + { + Self::install(db).await?; + let stmt = Query::select() + .table_name(Self::migration_table_name()) + .columns(seaql_migrations::Column::iter().map(IntoIden::into_iden)) + .order_by(seaql_migrations::Column::Version, Order::Asc) + .to_owned(); + let builder = db.get_database_backend(); + seaql_migrations::Model::find_by_statement(builder.build(&stmt)) + .all(db) + .await + } + + /// Get list of migrations with status + async fn get_migration_with_status(db: &C) -> Result, DbErr> + where + C: ConnectionTrait, + { + Self::install(db).await?; + let mut migration_files = Self::get_migration_files(); + let migration_models = Self::get_migration_models(db).await?; + + let migration_in_db: HashSet = migration_models + .into_iter() + .map(|model| model.version) + .collect(); + let migration_in_fs: HashSet = migration_files + .iter() + .map(|file| file.migration.name().to_string()) + .collect(); + + let pending_migrations = &migration_in_fs - &migration_in_db; + for migration_file in migration_files.iter_mut() { + if !pending_migrations.contains(migration_file.migration.name()) { + migration_file.status = MigrationStatus::Applied; + } + } + /* + let missing_migrations_in_fs = &migration_in_db - &migration_in_fs; + let errors: Vec = missing_migrations_in_fs + .iter() + .map(|missing_migration| { + format!("Migration file of version '{missing_migration}' is missing, this migration has been applied but its file is missing") + }).collect(); + + if !errors.is_empty() { + Err(DbErr::Custom(errors.join("\n"))) + } else { */ + Ok(migration_files) + /* } */ + } + + /// Get list of pending migrations + async fn get_pending_migrations(db: &C) -> Result, DbErr> + where + C: ConnectionTrait, + { + Self::install(db).await?; + Ok(Self::get_migration_with_status(db) + .await? + .into_iter() + .filter(|file| file.status == MigrationStatus::Pending) + .collect()) + } + + /// Get list of applied migrations + async fn get_applied_migrations(db: &C) -> Result, DbErr> + where + C: ConnectionTrait, + { + Self::install(db).await?; + Ok(Self::get_migration_with_status(db) + .await? + .into_iter() + .filter(|file| file.status == MigrationStatus::Applied) + .collect()) + } + + /// Create migration table `seaql_migrations` in the database + async fn install(db: &C) -> Result<(), DbErr> + where + C: ConnectionTrait, + { + let builder = db.get_database_backend(); + let table_name = Self::migration_table_name(); + let schema = Schema::new(builder); + let mut stmt = schema + .create_table_from_entity(seaql_migrations::Entity) + .table_name(table_name); + stmt.if_not_exists(); + db.execute(builder.build(&stmt)).await.map(|_| ()) + } + + /// Check the status of all migrations + async fn status(db: &C) -> Result<(), DbErr> + where + C: ConnectionTrait, + { + Self::install(db).await?; + + info!("Checking migration status"); + + for Migration { migration, status } in Self::get_migration_with_status(db).await? { + info!("Migration '{}'... {}", migration.name(), status); + } + + Ok(()) + } + + /// Drop all tables from the database, then reapply all migrations + async fn fresh<'c, C>(db: C) -> Result<(), DbErr> + where + C: IntoSchemaManagerConnection<'c>, + { + exec_with_connection::<'_, _, _>(db, move |manager| { + Box::pin(async move { exec_fresh::(manager).await }) + }) + .await + } + + /// Rollback all applied migrations, then reapply all migrations + async fn refresh<'c, C>(db: C) -> Result<(), DbErr> + where + C: IntoSchemaManagerConnection<'c>, + { + exec_with_connection::<'_, _, _>(db, move |manager| { + Box::pin(async move { + exec_down::(manager, None).await?; + exec_up::(manager, None).await + }) + }) + .await + } + + /// Rollback all applied migrations + async fn reset<'c, C>(db: C) -> Result<(), DbErr> + where + C: IntoSchemaManagerConnection<'c>, + { + exec_with_connection::<'_, _, _>(db, move |manager| { + Box::pin(async move { exec_down::(manager, None).await }) + }) + .await + } + + /// Apply pending migrations + async fn up<'c, C>(db: C, steps: Option) -> Result<(), DbErr> + where + C: IntoSchemaManagerConnection<'c>, + { + exec_with_connection::<'_, _, _>(db, move |manager| { + Box::pin(async move { exec_up::(manager, steps).await }) + }) + .await + } + + /// Rollback applied migrations + async fn down<'c, C>(db: C, steps: Option) -> Result<(), DbErr> + where + C: IntoSchemaManagerConnection<'c>, + { + exec_with_connection::<'_, _, _>(db, move |manager| { + Box::pin(async move { exec_down::(manager, steps).await }) + }) + .await + } +} + +async fn exec_with_connection<'c, C, F>(db: C, f: F) -> Result<(), DbErr> +where + C: IntoSchemaManagerConnection<'c>, + F: for<'b> Fn( + &'b SchemaManager<'_>, + ) -> Pin> + Send + 'b>>, +{ + let db = db.into_schema_manager_connection(); + + match db.get_database_backend() { + DbBackend::Postgres => { + let transaction = db.begin().await?; + let manager = SchemaManager::new(&transaction); + f(&manager).await?; + transaction.commit().await + } + DbBackend::MySql | DbBackend::Sqlite => { + let manager = SchemaManager::new(db); + f(&manager).await + } + } +} + +async fn exec_fresh(manager: &SchemaManager<'_>) -> Result<(), DbErr> +where + M: MigratorTrait + ?Sized, +{ + let db = manager.get_connection(); + + M::install(db).await?; + let db_backend = db.get_database_backend(); + + // Temporarily disable the foreign key check + if db_backend == DbBackend::Sqlite { + info!("Disabling foreign key check"); + db.execute(Statement::from_string( + db_backend, + "PRAGMA foreign_keys = OFF".to_owned(), + )) + .await?; + info!("Foreign key check disabled"); + } + + // Drop all foreign keys + if db_backend == DbBackend::MySql { + info!("Dropping all foreign keys"); + let stmt = query_mysql_foreign_keys(db); + let rows = db.query_all(db_backend.build(&stmt)).await?; + for row in rows.into_iter() { + let constraint_name: String = row.try_get("", "CONSTRAINT_NAME")?; + let table_name: String = row.try_get("", "TABLE_NAME")?; + info!( + "Dropping foreign key '{}' from table '{}'", + constraint_name, table_name + ); + let mut stmt = ForeignKey::drop(); + stmt.table(Alias::new(table_name.as_str())) + .name(constraint_name.as_str()); + db.execute(db_backend.build(&stmt)).await?; + info!("Foreign key '{}' has been dropped", constraint_name); + } + info!("All foreign keys dropped"); + } + + // Drop all tables + let stmt = query_tables(db).await; + let rows = db.query_all(db_backend.build(&stmt)).await?; + for row in rows.into_iter() { + let table_name: String = row.try_get("", "table_name")?; + info!("Dropping table '{}'", table_name); + let mut stmt = Table::drop(); + stmt.table(Alias::new(table_name.as_str())) + .if_exists() + .cascade(); + db.execute(db_backend.build(&stmt)).await?; + info!("Table '{}' has been dropped", table_name); + } + + // Drop all types + if db_backend == DbBackend::Postgres { + info!("Dropping all types"); + let stmt = query_pg_types(db); + let rows = db.query_all(db_backend.build(&stmt)).await?; + for row in rows { + let type_name: String = row.try_get("", "typname")?; + info!("Dropping type '{}'", type_name); + let mut stmt = Type::drop(); + stmt.name(Alias::new(&type_name)); + db.execute(db_backend.build(&stmt)).await?; + info!("Type '{}' has been dropped", type_name); + } + } + + // Restore the foreign key check + if db_backend == DbBackend::Sqlite { + info!("Restoring foreign key check"); + db.execute(Statement::from_string( + db_backend, + "PRAGMA foreign_keys = ON".to_owned(), + )) + .await?; + info!("Foreign key check restored"); + } + + // Reapply all migrations + exec_up::(manager, None).await +} + +async fn exec_up(manager: &SchemaManager<'_>, mut steps: Option) -> Result<(), DbErr> +where + M: MigratorTrait + ?Sized, +{ + let db = manager.get_connection(); + + M::install(db).await?; + /* + if let Some(steps) = steps { + info!("Applying {} pending migrations", steps); + } else { + info!("Applying all pending migrations"); + } + */ + let migrations = M::get_pending_migrations(db).await?.into_iter(); + /* + if migrations.len() == 0 { + info!("No pending migrations"); + } + */ + for Migration { migration, .. } in migrations { + if let Some(steps) = steps.as_mut() { + if steps == &0 { + break; + } + *steps -= 1; + } + info!("Applying migration '{}'", migration.name()); + migration.up(manager).await?; + info!("Migration '{}' has been applied", migration.name()); + let now = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("SystemTime before UNIX EPOCH!"); + seaql_migrations::Entity::insert(seaql_migrations::ActiveModel { + version: ActiveValue::Set(migration.name().to_owned()), + applied_at: ActiveValue::Set(now.as_secs() as i64), + }) + .table_name(M::migration_table_name()) + .exec(db) + .await?; + } + + Ok(()) +} + +async fn exec_down(manager: &SchemaManager<'_>, mut steps: Option) -> Result<(), DbErr> +where + M: MigratorTrait + ?Sized, +{ + let db = manager.get_connection(); + + M::install(db).await?; + + if let Some(steps) = steps { + info!("Rolling back {} applied migrations", steps); + } else { + info!("Rolling back all applied migrations"); + } + + let migrations = M::get_applied_migrations(db).await?.into_iter().rev(); + if migrations.len() == 0 { + info!("No applied migrations"); + } + for Migration { migration, .. } in migrations { + if let Some(steps) = steps.as_mut() { + if steps == &0 { + break; + } + *steps -= 1; + } + info!("Rolling back migration '{}'", migration.name()); + migration.down(manager).await?; + info!("Migration '{}' has been rollbacked", migration.name()); + seaql_migrations::Entity::delete_many() + .filter(Expr::col(seaql_migrations::Column::Version).eq(migration.name())) + .table_name(M::migration_table_name()) + .exec(db) + .await?; + } + + Ok(()) +} + +async fn query_tables(db: &C) -> SelectStatement +where + C: ConnectionTrait, +{ + match db.get_database_backend() { + DbBackend::MySql => MySql.query_tables(), + DbBackend::Postgres => Postgres.query_tables(), + DbBackend::Sqlite => Sqlite.query_tables(), + } +} + +fn get_current_schema(db: &C) -> SimpleExpr +where + C: ConnectionTrait, +{ + match db.get_database_backend() { + DbBackend::MySql => MySql::get_current_schema(), + DbBackend::Postgres => Postgres::get_current_schema(), + DbBackend::Sqlite => unimplemented!(), + } +} + +#[derive(DeriveIden)] +enum InformationSchema { + #[sea_orm(iden = "information_schema")] + Schema, + #[sea_orm(iden = "TABLE_NAME")] + TableName, + #[sea_orm(iden = "CONSTRAINT_NAME")] + ConstraintName, + TableConstraints, + TableSchema, + ConstraintType, +} + +fn query_mysql_foreign_keys(db: &C) -> SelectStatement +where + C: ConnectionTrait, +{ + let mut stmt = Query::select(); + stmt.columns([ + InformationSchema::TableName, + InformationSchema::ConstraintName, + ]) + .from(( + InformationSchema::Schema, + InformationSchema::TableConstraints, + )) + .cond_where( + Condition::all() + .add(Expr::expr(get_current_schema(db)).equals(( + InformationSchema::TableConstraints, + InformationSchema::TableSchema, + ))) + .add( + Expr::col(( + InformationSchema::TableConstraints, + InformationSchema::ConstraintType, + )) + .eq("FOREIGN KEY"), + ), + ); + stmt +} + +#[derive(DeriveIden)] +enum PgType { + Table, + Typname, + Typnamespace, + Typelem, +} + +#[derive(DeriveIden)] +enum PgNamespace { + Table, + Oid, + Nspname, +} + +fn query_pg_types(db: &C) -> SelectStatement +where + C: ConnectionTrait, +{ + let mut stmt = Query::select(); + stmt.column(PgType::Typname) + .from(PgType::Table) + .join( + JoinType::LeftJoin, + PgNamespace::Table, + Expr::col((PgNamespace::Table, PgNamespace::Oid)) + .equals((PgType::Table, PgType::Typnamespace)), + ) + .cond_where( + Condition::all() + .add( + Expr::expr(get_current_schema(db)) + .equals((PgNamespace::Table, PgNamespace::Nspname)), + ) + .add(Expr::col((PgType::Table, PgType::Typelem)).eq(0)), + ); + stmt +} + +trait QueryTable { + type Statement; + + fn table_name(self, table_name: DynIden) -> Self::Statement; +} + +impl QueryTable for SelectStatement { + type Statement = SelectStatement; + + fn table_name(mut self, table_name: DynIden) -> SelectStatement { + self.from(table_name); + self + } +} + +impl QueryTable for sea_query::TableCreateStatement { + type Statement = sea_query::TableCreateStatement; + + fn table_name(mut self, table_name: DynIden) -> sea_query::TableCreateStatement { + self.table(table_name); + self + } +} + +impl QueryTable for sea_orm::Insert +where + A: ActiveModelTrait, +{ + type Statement = sea_orm::Insert; + + fn table_name(mut self, table_name: DynIden) -> sea_orm::Insert { + sea_orm::QueryTrait::query(&mut self).into_table(table_name); + self + } +} + +impl QueryTable for sea_orm::DeleteMany +where + E: EntityTrait, +{ + type Statement = sea_orm::DeleteMany; + + fn table_name(mut self, table_name: DynIden) -> sea_orm::DeleteMany { + sea_orm::QueryTrait::query(&mut self).from_table(table_name); + self + } +} diff --git a/extensions/pagetop-seaorm/src/db/migration/prelude.rs b/extensions/pagetop-seaorm/src/db/migration/prelude.rs new file mode 100644 index 00000000..5556a094 --- /dev/null +++ b/extensions/pagetop-seaorm/src/db/migration/prelude.rs @@ -0,0 +1,13 @@ +//pub use super::cli; + +pub use super::connection::IntoSchemaManagerConnection; +pub use super::connection::SchemaManagerConnection; +pub use super::manager::SchemaManager; +pub use super::migrator::MigratorTrait; +pub use super::{MigrationName, MigrationTrait}; +pub use async_trait; +pub use sea_orm; +pub use sea_orm::sea_query; +pub use sea_orm::sea_query::*; +pub use sea_orm::DeriveIden; +pub use sea_orm::DeriveMigrationName; diff --git a/extensions/pagetop-seaorm/src/db/migration/schema.rs b/extensions/pagetop-seaorm/src/db/migration/schema.rs new file mode 100644 index 00000000..f250eae0 --- /dev/null +++ b/extensions/pagetop-seaorm/src/db/migration/schema.rs @@ -0,0 +1,608 @@ +//! Adapted from +//! +//! # Database Table Schema Helpers +//! +//! This module defines functions and helpers for creating database table +//! schemas using the `sea-orm` and `sea-query` libraries. +//! +//! # Example +//! +//! The following example shows how the user migration file should be and using +//! the schema helpers to create the Db fields. +//! +//! ```rust +//! use sea_orm_migration::{prelude::*, schema::*}; +//! +//! #[derive(DeriveMigrationName)] +//! pub struct Migration; +//! +//! #[async_trait::async_trait] +//! impl MigrationTrait for Migration { +//! async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { +//! let table = table_auto(Users::Table) +//! .col(pk_auto(Users::Id)) +//! .col(uuid(Users::Pid)) +//! .col(string_uniq(Users::Email)) +//! .col(string(Users::Password)) +//! .col(string(Users::Name)) +//! .col(string_null(Users::ResetToken)) +//! .col(timestamp_null(Users::ResetSentAt)) +//! .to_owned(); +//! manager.create_table(table).await?; +//! Ok(()) +//! } +//! +//! async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { +//! manager +//! .drop_table(Table::drop().table(Users::Table).to_owned()) +//! .await +//! } +//! } +//! +//! #[derive(Iden)] +//! pub enum Users { +//! Table, +//! Id, +//! Pid, +//! Email, +//! Name, +//! Password, +//! ResetToken, +//! ResetSentAt, +//! } +//! ``` + +use crate::prelude::Iden; +use sea_orm::sea_query::{ + self, Alias, ColumnDef, ColumnType, Expr, IntoIden, PgInterval, Table, TableCreateStatement, +}; + +#[derive(Iden)] +enum GeneralIds { + CreatedAt, + UpdatedAt, +} + +/// Wrapping table schema creation. +pub fn table_auto(name: T) -> TableCreateStatement { + timestamps(Table::create().table(name).if_not_exists().take()) +} + +/// Create a primary key column with auto-increment feature. +pub fn pk_auto(name: T) -> ColumnDef { + integer(name).auto_increment().primary_key().take() +} + +pub fn char_len(col: T, length: u32) -> ColumnDef { + ColumnDef::new(col).char_len(length).not_null().take() +} + +pub fn char_len_null(col: T, length: u32) -> ColumnDef { + ColumnDef::new(col).char_len(length).null().take() +} + +pub fn char_len_uniq(col: T, length: u32) -> ColumnDef { + char_len(col, length).unique_key().take() +} + +pub fn char(col: T) -> ColumnDef { + ColumnDef::new(col).char().not_null().take() +} + +pub fn char_null(col: T) -> ColumnDef { + ColumnDef::new(col).char().null().take() +} + +pub fn char_uniq(col: T) -> ColumnDef { + char(col).unique_key().take() +} + +pub fn string_len(col: T, length: u32) -> ColumnDef { + ColumnDef::new(col).string_len(length).not_null().take() +} + +pub fn string_len_null(col: T, length: u32) -> ColumnDef { + ColumnDef::new(col).string_len(length).null().take() +} + +pub fn string_len_uniq(col: T, length: u32) -> ColumnDef { + string_len(col, length).unique_key().take() +} + +pub fn string(col: T) -> ColumnDef { + ColumnDef::new(col).string().not_null().take() +} + +pub fn string_null(col: T) -> ColumnDef { + ColumnDef::new(col).string().null().take() +} + +pub fn string_uniq(col: T) -> ColumnDef { + string(col).unique_key().take() +} + +pub fn text(col: T) -> ColumnDef { + ColumnDef::new(col).text().not_null().take() +} + +pub fn text_null(col: T) -> ColumnDef { + ColumnDef::new(col).text().null().take() +} + +pub fn text_uniq(col: T) -> ColumnDef { + text(col).unique_key().take() +} + +pub fn tiny_integer(col: T) -> ColumnDef { + ColumnDef::new(col).tiny_integer().not_null().take() +} + +pub fn tiny_integer_null(col: T) -> ColumnDef { + ColumnDef::new(col).tiny_integer().null().take() +} + +pub fn tiny_integer_uniq(col: T) -> ColumnDef { + tiny_integer(col).unique_key().take() +} + +pub fn small_integer(col: T) -> ColumnDef { + ColumnDef::new(col).small_integer().not_null().take() +} + +pub fn small_integer_null(col: T) -> ColumnDef { + ColumnDef::new(col).small_integer().null().take() +} + +pub fn small_integer_uniq(col: T) -> ColumnDef { + small_integer(col).unique_key().take() +} + +pub fn integer(col: T) -> ColumnDef { + ColumnDef::new(col).integer().not_null().take() +} + +pub fn integer_null(col: T) -> ColumnDef { + ColumnDef::new(col).integer().null().take() +} + +pub fn integer_uniq(col: T) -> ColumnDef { + integer(col).unique_key().take() +} + +pub fn big_integer(col: T) -> ColumnDef { + ColumnDef::new(col).big_integer().not_null().take() +} + +pub fn big_integer_null(col: T) -> ColumnDef { + ColumnDef::new(col).big_integer().null().take() +} + +pub fn big_integer_uniq(col: T) -> ColumnDef { + big_integer(col).unique_key().take() +} + +pub fn tiny_unsigned(col: T) -> ColumnDef { + ColumnDef::new(col).tiny_unsigned().not_null().take() +} + +pub fn tiny_unsigned_null(col: T) -> ColumnDef { + ColumnDef::new(col).tiny_unsigned().null().take() +} + +pub fn tiny_unsigned_uniq(col: T) -> ColumnDef { + tiny_unsigned(col).unique_key().take() +} + +pub fn small_unsigned(col: T) -> ColumnDef { + ColumnDef::new(col).small_unsigned().not_null().take() +} + +pub fn small_unsigned_null(col: T) -> ColumnDef { + ColumnDef::new(col).small_unsigned().null().take() +} + +pub fn small_unsigned_uniq(col: T) -> ColumnDef { + small_unsigned(col).unique_key().take() +} + +pub fn unsigned(col: T) -> ColumnDef { + ColumnDef::new(col).unsigned().not_null().take() +} + +pub fn unsigned_null(col: T) -> ColumnDef { + ColumnDef::new(col).unsigned().null().take() +} + +pub fn unsigned_uniq(col: T) -> ColumnDef { + unsigned(col).unique_key().take() +} + +pub fn big_unsigned(col: T) -> ColumnDef { + ColumnDef::new(col).big_unsigned().not_null().take() +} + +pub fn big_unsigned_null(col: T) -> ColumnDef { + ColumnDef::new(col).big_unsigned().null().take() +} + +pub fn big_unsigned_uniq(col: T) -> ColumnDef { + big_unsigned(col).unique_key().take() +} + +pub fn float(col: T) -> ColumnDef { + ColumnDef::new(col).float().not_null().take() +} + +pub fn float_null(col: T) -> ColumnDef { + ColumnDef::new(col).float().null().take() +} + +pub fn float_uniq(col: T) -> ColumnDef { + float(col).unique_key().take() +} + +pub fn double(col: T) -> ColumnDef { + ColumnDef::new(col).double().not_null().take() +} + +pub fn double_null(col: T) -> ColumnDef { + ColumnDef::new(col).double().null().take() +} + +pub fn double_uniq(col: T) -> ColumnDef { + double(col).unique_key().take() +} + +pub fn decimal_len(col: T, precision: u32, scale: u32) -> ColumnDef { + ColumnDef::new(col) + .decimal_len(precision, scale) + .not_null() + .take() +} + +pub fn decimal_len_null(col: T, precision: u32, scale: u32) -> ColumnDef { + ColumnDef::new(col) + .decimal_len(precision, scale) + .null() + .take() +} + +pub fn decimal_len_uniq(col: T, precision: u32, scale: u32) -> ColumnDef { + decimal_len(col, precision, scale).unique_key().take() +} + +pub fn decimal(col: T) -> ColumnDef { + ColumnDef::new(col).decimal().not_null().take() +} + +pub fn decimal_null(col: T) -> ColumnDef { + ColumnDef::new(col).decimal().null().take() +} + +pub fn decimal_uniq(col: T) -> ColumnDef { + decimal(col).unique_key().take() +} + +pub fn date_time(col: T) -> ColumnDef { + ColumnDef::new(col).date_time().not_null().take() +} + +pub fn date_time_null(col: T) -> ColumnDef { + ColumnDef::new(col).date_time().null().take() +} + +pub fn date_time_uniq(col: T) -> ColumnDef { + date_time(col).unique_key().take() +} + +pub fn interval( + col: T, + fields: Option, + precision: Option, +) -> ColumnDef { + ColumnDef::new(col) + .interval(fields, precision) + .not_null() + .take() +} + +pub fn interval_null( + col: T, + fields: Option, + precision: Option, +) -> ColumnDef { + ColumnDef::new(col) + .interval(fields, precision) + .null() + .take() +} + +pub fn interval_uniq( + col: T, + fields: Option, + precision: Option, +) -> ColumnDef { + interval(col, fields, precision).unique_key().take() +} + +pub fn timestamp(col: T) -> ColumnDef { + ColumnDef::new(col).timestamp().not_null().take() +} + +pub fn timestamp_null(col: T) -> ColumnDef { + ColumnDef::new(col).timestamp().null().take() +} + +pub fn timestamp_uniq(col: T) -> ColumnDef { + timestamp(col).unique_key().take() +} + +pub fn timestamp_with_time_zone(col: T) -> ColumnDef { + ColumnDef::new(col) + .timestamp_with_time_zone() + .not_null() + .take() +} + +pub fn timestamp_with_time_zone_null(col: T) -> ColumnDef { + ColumnDef::new(col).timestamp_with_time_zone().null().take() +} + +pub fn timestamp_with_time_zone_uniq(col: T) -> ColumnDef { + timestamp_with_time_zone(col).unique_key().take() +} + +pub fn time(col: T) -> ColumnDef { + ColumnDef::new(col).time().not_null().take() +} + +pub fn time_null(col: T) -> ColumnDef { + ColumnDef::new(col).time().null().take() +} + +pub fn time_uniq(col: T) -> ColumnDef { + time(col).unique_key().take() +} + +pub fn date(col: T) -> ColumnDef { + ColumnDef::new(col).date().not_null().take() +} + +pub fn date_null(col: T) -> ColumnDef { + ColumnDef::new(col).date().null().take() +} + +pub fn date_uniq(col: T) -> ColumnDef { + date(col).unique_key().take() +} + +pub fn year(col: T) -> ColumnDef { + ColumnDef::new(col).year().not_null().take() +} + +pub fn year_null(col: T) -> ColumnDef { + ColumnDef::new(col).year().null().take() +} + +pub fn year_uniq(col: T) -> ColumnDef { + year(col).unique_key().take() +} + +pub fn binary_len(col: T, length: u32) -> ColumnDef { + ColumnDef::new(col).binary_len(length).not_null().take() +} + +pub fn binary_len_null(col: T, length: u32) -> ColumnDef { + ColumnDef::new(col).binary_len(length).null().take() +} + +pub fn binary_len_uniq(col: T, length: u32) -> ColumnDef { + binary_len(col, length).unique_key().take() +} + +pub fn binary(col: T) -> ColumnDef { + ColumnDef::new(col).binary().not_null().take() +} + +pub fn binary_null(col: T) -> ColumnDef { + ColumnDef::new(col).binary().null().take() +} + +pub fn binary_uniq(col: T) -> ColumnDef { + binary(col).unique_key().take() +} + +pub fn var_binary(col: T, length: u32) -> ColumnDef { + ColumnDef::new(col).var_binary(length).not_null().take() +} + +pub fn var_binary_null(col: T, length: u32) -> ColumnDef { + ColumnDef::new(col).var_binary(length).null().take() +} + +pub fn var_binary_uniq(col: T, length: u32) -> ColumnDef { + var_binary(col, length).unique_key().take() +} + +pub fn bit(col: T, length: Option) -> ColumnDef { + ColumnDef::new(col).bit(length).not_null().take() +} + +pub fn bit_null(col: T, length: Option) -> ColumnDef { + ColumnDef::new(col).bit(length).null().take() +} + +pub fn bit_uniq(col: T, length: Option) -> ColumnDef { + bit(col, length).unique_key().take() +} + +pub fn varbit(col: T, length: u32) -> ColumnDef { + ColumnDef::new(col).varbit(length).not_null().take() +} + +pub fn varbit_null(col: T, length: u32) -> ColumnDef { + ColumnDef::new(col).varbit(length).null().take() +} + +pub fn varbit_uniq(col: T, length: u32) -> ColumnDef { + varbit(col, length).unique_key().take() +} + +pub fn blob(col: T) -> ColumnDef { + ColumnDef::new(col).blob().not_null().take() +} + +pub fn blob_null(col: T) -> ColumnDef { + ColumnDef::new(col).blob().null().take() +} + +pub fn blob_uniq(col: T) -> ColumnDef { + blob(col).unique_key().take() +} + +pub fn boolean(col: T) -> ColumnDef { + ColumnDef::new(col).boolean().not_null().take() +} + +pub fn boolean_null(col: T) -> ColumnDef { + ColumnDef::new(col).boolean().null().take() +} + +pub fn boolean_uniq(col: T) -> ColumnDef { + boolean(col).unique_key().take() +} + +pub fn money_len(col: T, precision: u32, scale: u32) -> ColumnDef { + ColumnDef::new(col) + .money_len(precision, scale) + .not_null() + .take() +} + +pub fn money_len_null(col: T, precision: u32, scale: u32) -> ColumnDef { + ColumnDef::new(col) + .money_len(precision, scale) + .null() + .take() +} + +pub fn money_len_uniq(col: T, precision: u32, scale: u32) -> ColumnDef { + money_len(col, precision, scale).unique_key().take() +} + +pub fn money(col: T) -> ColumnDef { + ColumnDef::new(col).money().not_null().take() +} + +pub fn money_null(col: T) -> ColumnDef { + ColumnDef::new(col).money().null().take() +} + +pub fn money_uniq(col: T) -> ColumnDef { + money(col).unique_key().take() +} + +pub fn json(col: T) -> ColumnDef { + ColumnDef::new(col).json().not_null().take() +} + +pub fn json_null(col: T) -> ColumnDef { + ColumnDef::new(col).json().null().take() +} + +pub fn json_uniq(col: T) -> ColumnDef { + json(col).unique_key().take() +} + +pub fn json_binary(col: T) -> ColumnDef { + ColumnDef::new(col).json_binary().not_null().take() +} + +pub fn json_binary_null(col: T) -> ColumnDef { + ColumnDef::new(col).json_binary().null().take() +} + +pub fn json_binary_uniq(col: T) -> ColumnDef { + json_binary(col).unique_key().take() +} + +pub fn uuid(col: T) -> ColumnDef { + ColumnDef::new(col).uuid().not_null().take() +} + +pub fn uuid_null(col: T) -> ColumnDef { + ColumnDef::new(col).uuid().null().take() +} + +pub fn uuid_uniq(col: T) -> ColumnDef { + uuid(col).unique_key().take() +} + +pub fn custom(col: T, name: T) -> ColumnDef { + ColumnDef::new(col).custom(name).not_null().take() +} + +pub fn custom_null(col: T, name: T) -> ColumnDef { + ColumnDef::new(col).custom(name).null().take() +} + +pub fn enumeration(col: T, name: N, variants: V) -> ColumnDef +where + T: IntoIden, + N: IntoIden, + S: IntoIden, + V: IntoIterator, +{ + ColumnDef::new(col) + .enumeration(name, variants) + .not_null() + .take() +} + +pub fn enumeration_null(col: T, name: N, variants: V) -> ColumnDef +where + T: IntoIden, + N: IntoIden, + S: IntoIden, + V: IntoIterator, +{ + ColumnDef::new(col) + .enumeration(name, variants) + .null() + .take() +} + +pub fn enumeration_uniq(col: T, name: N, variants: V) -> ColumnDef +where + T: IntoIden, + N: IntoIden, + S: IntoIden, + V: IntoIterator, +{ + enumeration(col, name, variants).unique_key().take() +} + +pub fn array(col: T, elem_type: ColumnType) -> ColumnDef { + ColumnDef::new(col).array(elem_type).not_null().take() +} + +pub fn array_null(col: T, elem_type: ColumnType) -> ColumnDef { + ColumnDef::new(col).array(elem_type).null().take() +} + +pub fn array_uniq(col: T, elem_type: ColumnType) -> ColumnDef { + array(col, elem_type).unique_key().take() +} + +/// Add timestamp columns (`CreatedAt` and `UpdatedAt`) to an existing table. +pub fn timestamps(t: TableCreateStatement) -> TableCreateStatement { + let mut t = t; + t.col(timestamp(GeneralIds::CreatedAt).default(Expr::current_timestamp())) + .col(timestamp(GeneralIds::UpdatedAt).default(Expr::current_timestamp())) + .take() +} + +/// Create an Alias. +pub fn name>(name: T) -> Alias { + Alias::new(name) +} diff --git a/extensions/pagetop-seaorm/src/db/migration/seaql_migrations.rs b/extensions/pagetop-seaorm/src/db/migration/seaql_migrations.rs new file mode 100644 index 00000000..51da9300 --- /dev/null +++ b/extensions/pagetop-seaorm/src/db/migration/seaql_migrations.rs @@ -0,0 +1,15 @@ +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +// One should override the name of migration table via `MigratorTrait::migration_table_name` method +#[sea_orm(table_name = "seaql_migrations")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub version: String, + pub applied_at: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/extensions/pagetop-seaorm/src/lib.rs b/extensions/pagetop-seaorm/src/lib.rs new file mode 100644 index 00000000..b9928727 --- /dev/null +++ b/extensions/pagetop-seaorm/src/lib.rs @@ -0,0 +1,29 @@ +use pagetop::prelude::*; + +pub mod config; +pub mod db; + +/// Preludio de la extensión. +pub mod prelude { + pub use crate::db::*; + pub use crate::install_migrations; +} + +include_locales!(LOCALES_SEAORM); + +/// Extensión que integra SeaORM como framework de base de datos para aplicaciones PageTop. +pub struct SeaORM; + +impl Extension for SeaORM { + fn name(&self) -> L10n { + L10n::t("extension_name", &LOCALES_SEAORM) + } + + fn description(&self) -> L10n { + L10n::t("extension_description", &LOCALES_SEAORM) + } + + fn initialize(&self) { + std::sync::LazyLock::force(&db::DBCONN); + } +} diff --git a/extensions/pagetop-seaorm/src/locale/en-US/extension.ftl b/extensions/pagetop-seaorm/src/locale/en-US/extension.ftl new file mode 100644 index 00000000..f80d7aa4 --- /dev/null +++ b/extensions/pagetop-seaorm/src/locale/en-US/extension.ftl @@ -0,0 +1,2 @@ +extension_name = SeaORM support +extension_description = Provides SeaORM-based access to relational databases. diff --git a/extensions/pagetop-seaorm/src/locale/es-ES/extension.ftl b/extensions/pagetop-seaorm/src/locale/es-ES/extension.ftl new file mode 100644 index 00000000..e48e4653 --- /dev/null +++ b/extensions/pagetop-seaorm/src/locale/es-ES/extension.ftl @@ -0,0 +1,2 @@ +extension_name = Soporte a SeaORM +extension_description = Proporciona acceso basado en SeaORM a bases de datos relacionales. diff --git a/tools/changelog.sh b/tools/changelog.sh index 36fd8f9b..fc2a8a0c 100755 --- a/tools/changelog.sh +++ b/tools/changelog.sh @@ -65,6 +65,7 @@ case "$CRATE" in # Extensions --exclude-path "extensions/pagetop-aliner/**/*" --exclude-path "extensions/pagetop-bootsier/**/*" + --exclude-path "extensions/pagetop-seaorm/**/*" ) ;; pagetop-aliner) @@ -75,6 +76,10 @@ case "$CRATE" in CHANGELOG_FILE="extensions/pagetop-bootsier/CHANGELOG.md" PATH_FLAGS=(--include-path "extensions/pagetop-bootsier/**/*") ;; + pagetop-seaorm) + CHANGELOG_FILE="extensions/pagetop-seaorm/CHANGELOG.md" + PATH_FLAGS=(--include-path "extensions/pagetop-seaorm/**/*") + ;; *) echo "Error: unsupported crate '$CRATE'" >&2 exit 1 @@ -120,7 +125,9 @@ read -r -p "Do you want to proceed with the release of $CRATE? [y/N] " REPLY echo "" if [[ ! "$REPLY" =~ ^[Yy]$ ]]; then echo "Aborting release process." >&2 - git restore --worktree -- . + if [[ -n "${PAGETOP_RESTORE_TREE:-}" ]]; then + git restore --worktree -- . + fi exit 1 fi diff --git a/tools/release.sh b/tools/release.sh index 92257b32..5234c849 100755 --- a/tools/release.sh +++ b/tools/release.sh @@ -43,6 +43,8 @@ cd "$(dirname "$0")/.." || exit 1 # ------------------------------------------------------------------------------ # DRY-RUN (por defecto) o ejecución real con --execute # ------------------------------------------------------------------------------ +export PAGETOP_RESTORE_TREE=1 + if [[ "$EXECUTE" != "--execute" ]]; then echo "Running dry-run (default mode). Add --execute to publish" cargo release --config "$CONFIG" --package "$CRATE" "$LEVEL" From bd8a34341d225e903a1c699401f21d2f4e4374fe Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sat, 9 May 2026 13:33:20 +0200 Subject: [PATCH 06/12] =?UTF-8?q?=F0=9F=9A=A7=20Retoques=20en=20documentac?= =?UTF-8?q?i=C3=B3n=20y=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/pagetop-aliner/src/lib.rs | 5 +- extensions/pagetop-seaorm/src/config.rs | 22 ++--- extensions/pagetop-seaorm/src/lib.rs | 113 +++++++++++++++++++++++- 3 files changed, 119 insertions(+), 21 deletions(-) diff --git a/extensions/pagetop-aliner/src/lib.rs b/extensions/pagetop-aliner/src/lib.rs index 7a29eebc..e88a9142 100644 --- a/extensions/pagetop-aliner/src/lib.rs +++ b/extensions/pagetop-aliner/src/lib.rs @@ -85,9 +85,10 @@ use pagetop::prelude::*; include_locales!(LOCALES_ALINER); -/// Implementa el tema para usar en pruebas que muestran el esquema de páginas HTML. +/// Implementa el tema. /// -/// Define un tema mínimo útil para: +/// Define un tema mínimo que muestra esquemáticamente la composición de las páginas HTML; útil +/// para: /// /// - Comprobar el funcionamiento de temas, plantillas y regiones. /// - Verificar integración de componentes y composiciones (*layouts*) sin estilos complejos. diff --git a/extensions/pagetop-seaorm/src/config.rs b/extensions/pagetop-seaorm/src/config.rs index 5c706659..bec565b3 100644 --- a/extensions/pagetop-seaorm/src/config.rs +++ b/extensions/pagetop-seaorm/src/config.rs @@ -16,8 +16,7 @@ //! Uso: //! //! ```rust -//! use pagetop_seaorm::config; -//! +//! # use pagetop_seaorm::config; //! assert_eq!(config::SETTINGS.database.db_host, "localhost"); //! ``` //! @@ -30,12 +29,12 @@ use serde::Deserialize; include_config!(SETTINGS: Settings => [ // [database] - "database.db_type" => "", - "database.db_name" => "", - "database.db_user" => "", - "database.db_pass" => "", - "database.db_host" => "localhost", - "database.db_port" => 0, + "database.db_type" => "", + "database.db_name" => "", + "database.db_user" => "", + "database.db_pass" => "", + "database.db_host" => "localhost", + "database.db_port" => 0, "database.max_pool_size" => 5, ]); @@ -49,24 +48,17 @@ pub struct Settings { /// Sección `[database]` de la configuración. Forma parte de [`Settings`]. pub struct Database { /// Tipo de base de datos: *"mysql"*, *"postgres"* ó *"sqlite"*. - /// Por defecto: *""*. pub db_type: String, /// Nombre (para mysql/postgres) o referencia (para sqlite) de la base de datos. - /// Por defecto: *""*. pub db_name: String, /// Usuario de conexión a la base de datos (para mysql/postgres). - /// Por defecto: *""*. pub db_user: String, /// Contraseña para la conexión a la base de datos (para mysql/postgres). - /// Por defecto: *""*. pub db_pass: String, /// Servidor de conexión a la base de datos (para mysql/postgres). - /// Por defecto: *"localhost"*. pub db_host: String, /// Puerto de conexión a la base de datos, normalmente 3306 (para mysql) ó 5432 (para postgres). - /// Por defecto: *0*. pub db_port: u16, /// Número máximo de conexiones habilitadas. - /// Por defecto: *5*. pub max_pool_size: u32, } diff --git a/extensions/pagetop-seaorm/src/lib.rs b/extensions/pagetop-seaorm/src/lib.rs index b9928727..64578cb8 100644 --- a/extensions/pagetop-seaorm/src/lib.rs +++ b/extensions/pagetop-seaorm/src/lib.rs @@ -1,17 +1,122 @@ +/*! +
+ +

PageTop SeaORM

+ +

Proporciona a PageTop acceso basado en SeaORM a bases de datos relacionales.

+ +[![Doc API](https://img.shields.io/docsrs/pagetop-seaorm?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-seaorm) +[![Crates.io](https://img.shields.io/crates/v/pagetop-seaorm.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-seaorm) +[![Descargas](https://img.shields.io/crates/d/pagetop-seaorm.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-seaorm) +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/extensions/pagetop-seaorm#licencia) + +
+ +## 🧭 Sobre PageTop + +[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web +clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y +configurables, basadas en HTML, CSS y JavaScript. + + +## ⚡️ Guía rápida + +**Añade la dependencia** a tu `Cargo.toml` activando el motor de base de datos que necesites: + +```toml +[dependencies] +pagetop-seaorm = { version = "...", features = ["sqlite"] } +``` + +Las *features* disponibles son `mysql`, `postgres` y `sqlite`. + +**Configura la conexión** en el archivo de configuración de la aplicación: + +```toml +[database] +db_type = "sqlite" +db_name = "my_app.db" +max_pool_size = 5 +``` + +Para MySQL o PostgreSQL añade también `db_user`, `db_pass`, `db_host` y `db_port`. + +**Declara la extensión** en tu aplicación o en la extensión que la requiera: + +```rust,no_run use pagetop::prelude::*; +struct MyApp; + +impl Extension for MyApp { + fn dependencies(&self) -> Vec { + vec![ + &pagetop_seaorm::SeaORM, + ] + } + + fn initialize(&self) { + install_migrations!(m20240101_000001_create_users_table); + } +} + +#[pagetop::main] +async fn main() -> std::io::Result<()> { + Application::prepare(&MyApp).run()?.await +} +``` + +**Escribe las migraciones** usando la API de SeaORM: + +```rust,no_run +use pagetop_seaorm::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + table_auto(Users::Table) + .col(pk_auto(Users::Id)) + .col(string_uniq(Users::Email)) + .to_owned(), + ) + .await + } +} + +#[derive(DeriveIden)] +enum Users { + Table, + Id, + Email, +} +``` +*/ + +#![doc( + html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico" +)] + +use pagetop::prelude::*; + +include_locales!(LOCALES_SEAORM); + pub mod config; + pub mod db; -/// Preludio de la extensión. +/// *Prelude* de la extensión. pub mod prelude { + pub use crate::config::*; pub use crate::db::*; pub use crate::install_migrations; } -include_locales!(LOCALES_SEAORM); - -/// Extensión que integra SeaORM como framework de base de datos para aplicaciones PageTop. +/// Implementa la extensión. pub struct SeaORM; impl Extension for SeaORM { From a0805ed0fb00c1f9ebad77ac50b3361f5afaa098 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 10 May 2026 00:31:33 +0200 Subject: [PATCH 07/12] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(bootsier):=20Elimin?= =?UTF-8?q?a=20`prelude`=20para=20usar=20`theme`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/form-controls.rs | 2 +- examples/navbar-menus.rs | 2 +- extensions/pagetop-bootsier/src/lib.rs | 6 - .../src/theme/attrs/border.rs | 2 +- .../src/theme/attrs/breakpoint.rs | 2 +- .../src/theme/attrs/button.rs | 4 +- .../pagetop-bootsier/src/theme/attrs/color.rs | 10 +- .../src/theme/attrs/layout.rs | 2 +- .../src/theme/attrs/rounded.rs | 4 +- .../pagetop-bootsier/src/theme/button.rs | 5 +- .../src/theme/classes/border.rs | 28 ++-- .../src/theme/classes/color.rs | 14 +- .../src/theme/classes/layout.rs | 6 +- .../src/theme/classes/rounded.rs | 26 +--- .../pagetop-bootsier/src/theme/container.rs | 10 -- .../src/theme/container/component.rs | 20 ++- .../pagetop-bootsier/src/theme/dropdown.rs | 19 +-- .../src/theme/dropdown/component.rs | 29 +++- .../src/theme/dropdown/props.rs | 2 +- extensions/pagetop-bootsier/src/theme/form.rs | 32 ----- .../pagetop-bootsier/src/theme/form/check.rs | 4 +- .../src/theme/form/checkbox.rs | 2 +- .../src/theme/form/component.rs | 38 ++++-- .../src/theme/form/fieldset.rs | 2 +- .../pagetop-bootsier/src/theme/form/hidden.rs | 2 +- .../pagetop-bootsier/src/theme/form/input.rs | 2 +- .../pagetop-bootsier/src/theme/form/props.rs | 4 +- .../pagetop-bootsier/src/theme/form/radio.rs | 4 +- .../pagetop-bootsier/src/theme/form/range.rs | 2 +- .../pagetop-bootsier/src/theme/form/select.rs | 6 +- .../src/theme/form/textarea.rs | 2 +- extensions/pagetop-bootsier/src/theme/icon.rs | 2 +- .../src/theme/image/component.rs | 12 +- extensions/pagetop-bootsier/src/theme/nav.rs | 22 +-- .../src/theme/nav/component.rs | 30 ++++- .../pagetop-bootsier/src/theme/nav/item.rs | 2 +- .../pagetop-bootsier/src/theme/navbar.rs | 122 +---------------- .../src/theme/navbar/brand.rs | 2 +- .../src/theme/navbar/component.rs | 126 +++++++++++++++++- .../pagetop-bootsier/src/theme/navbar/item.rs | 2 +- .../src/theme/navbar/props.rs | 2 +- .../pagetop-bootsier/src/theme/offcanvas.rs | 22 +-- .../src/theme/offcanvas/component.rs | 26 +++- 43 files changed, 315 insertions(+), 348 deletions(-) diff --git a/examples/form-controls.rs b/examples/form-controls.rs index e49844e8..4a6fc6c0 100644 --- a/examples/form-controls.rs +++ b/examples/form-controls.rs @@ -1,6 +1,6 @@ use pagetop::prelude::*; -use pagetop_bootsier::prelude::*; +use pagetop_bootsier::theme::*; include_locales!(LOC from "examples/locale"); diff --git a/examples/navbar-menus.rs b/examples/navbar-menus.rs index a0d85f3b..38918aed 100644 --- a/examples/navbar-menus.rs +++ b/examples/navbar-menus.rs @@ -1,6 +1,6 @@ use pagetop::prelude::*; -use pagetop_bootsier::prelude::*; +use pagetop_bootsier::theme::*; include_locales!(LOC from "examples/locale"); diff --git a/extensions/pagetop-bootsier/src/lib.rs b/extensions/pagetop-bootsier/src/lib.rs index b07b42d5..ca2a80c8 100644 --- a/extensions/pagetop-bootsier/src/lib.rs +++ b/extensions/pagetop-bootsier/src/lib.rs @@ -96,12 +96,6 @@ pub mod config; pub mod theme; -/// *Prelude* del tema. -pub mod prelude { - pub use crate::config::*; - pub use crate::theme::*; -} - /// Plantillas que Bootsier añade. #[derive(AutoDefault)] pub enum BootsierTemplate { diff --git a/extensions/pagetop-bootsier/src/theme/attrs/border.rs b/extensions/pagetop-bootsier/src/theme/attrs/border.rs index b46a5c6b..af66db78 100644 --- a/extensions/pagetop-bootsier/src/theme/attrs/border.rs +++ b/extensions/pagetop-bootsier/src/theme/attrs/border.rs @@ -58,7 +58,7 @@ impl BorderColor { /// # Ejemplos /// /// ```rust - /// # use pagetop_bootsier::prelude::*; + /// # use pagetop_bootsier::theme::*; /// assert_eq!(BorderColor::Theme(Color::Primary).to_class(), "border-primary"); /// assert_eq!(BorderColor::Subtle(Color::Warning).to_class(), "border-warning-subtle"); /// assert_eq!(BorderColor::Black.to_class(), "border-black"); diff --git a/extensions/pagetop-bootsier/src/theme/attrs/breakpoint.rs b/extensions/pagetop-bootsier/src/theme/attrs/breakpoint.rs index 992f8525..ec2c8568 100644 --- a/extensions/pagetop-bootsier/src/theme/attrs/breakpoint.rs +++ b/extensions/pagetop-bootsier/src/theme/attrs/breakpoint.rs @@ -70,7 +70,7 @@ impl BreakPoint { /// # Ejemplos /// /// ```rust - /// # use pagetop_bootsier::prelude::*; + /// # use pagetop_bootsier::theme::*; /// let bp = BreakPoint::MD; /// assert_eq!(bp.class_with("col", ""), "col-md"); /// assert_eq!(bp.class_with("col", "6"), "col-md-6"); diff --git a/extensions/pagetop-bootsier/src/theme/attrs/button.rs b/extensions/pagetop-bootsier/src/theme/attrs/button.rs index dc74fbea..01e0dd57 100644 --- a/extensions/pagetop-bootsier/src/theme/attrs/button.rs +++ b/extensions/pagetop-bootsier/src/theme/attrs/button.rs @@ -76,7 +76,7 @@ impl ButtonColor { /// # Ejemplos /// /// ```rust - /// # use pagetop_bootsier::prelude::*; + /// # use pagetop_bootsier::theme::*; /// assert_eq!( /// ButtonColor::Background(Color::Primary).to_class(), /// "btn-primary" @@ -132,7 +132,7 @@ impl ButtonSize { /// # Ejemplos /// /// ```rust - /// # use pagetop_bootsier::prelude::*; + /// # use pagetop_bootsier::theme::*; /// assert_eq!(ButtonSize::Small.to_class(), "btn-sm"); /// assert_eq!(ButtonSize::Large.to_class(), "btn-lg"); /// assert_eq!(ButtonSize::Default.to_class(), ""); diff --git a/extensions/pagetop-bootsier/src/theme/attrs/color.rs b/extensions/pagetop-bootsier/src/theme/attrs/color.rs index c408aadc..eb9f57d4 100644 --- a/extensions/pagetop-bootsier/src/theme/attrs/color.rs +++ b/extensions/pagetop-bootsier/src/theme/attrs/color.rs @@ -44,7 +44,7 @@ impl Color { /// # Ejemplos /// /// ```rust - /// # use pagetop_bootsier::prelude::*; + /// # use pagetop_bootsier::theme::*; /// assert_eq!(Color::Primary.to_class(), "primary"); /// assert_eq!(Color::Danger.to_class(), "danger"); /// ``` @@ -124,7 +124,7 @@ impl Opacity { /// # Ejemplos /// /// ```rust - /// # use pagetop_bootsier::prelude::*; + /// # use pagetop_bootsier::theme::*; /// assert_eq!(Opacity::Opaque.class_with(""), "opacity-100"); /// assert_eq!(Opacity::Half.class_with("bg"), "bg-opacity-50"); /// assert_eq!(Opacity::SemiTransparent.class_with("text"), "text-opacity-25"); @@ -156,7 +156,7 @@ impl Opacity { /// # Ejemplos /// /// ```rust - /// # use pagetop_bootsier::prelude::*; + /// # use pagetop_bootsier::theme::*; /// assert_eq!(Opacity::Opaque.to_class(), "opacity-100"); /// assert_eq!(Opacity::Half.to_class(), "opacity-50"); /// assert_eq!(Opacity::Default.to_class(), ""); @@ -237,7 +237,7 @@ impl ColorBg { /// # Ejemplos /// /// ```rust - /// # use pagetop_bootsier::prelude::*; + /// # use pagetop_bootsier::theme::*; /// assert_eq!(ColorBg::Body.to_class(), "bg-body"); /// assert_eq!(ColorBg::Theme(Color::Primary).to_class(), "bg-primary"); /// assert_eq!(ColorBg::Subtle(Color::Warning).to_class(), "bg-warning-subtle"); @@ -321,7 +321,7 @@ impl ColorText { /// # Ejemplos /// /// ```rust - /// # use pagetop_bootsier::prelude::*; + /// # use pagetop_bootsier::theme::*; /// assert_eq!(ColorText::Body.to_class(), "text-body"); /// assert_eq!(ColorText::Theme(Color::Primary).to_class(), "text-primary"); /// assert_eq!(ColorText::Emphasis(Color::Danger).to_class(), "text-danger-emphasis"); diff --git a/extensions/pagetop-bootsier/src/theme/attrs/layout.rs b/extensions/pagetop-bootsier/src/theme/attrs/layout.rs index a1255dc0..81b07834 100644 --- a/extensions/pagetop-bootsier/src/theme/attrs/layout.rs +++ b/extensions/pagetop-bootsier/src/theme/attrs/layout.rs @@ -61,7 +61,7 @@ impl ScaleSize { /// # Ejemplo /// /// ```rust - /// # use pagetop_bootsier::prelude::*; + /// # use pagetop_bootsier::theme::*; /// assert_eq!(ScaleSize::Auto.class_with("border"), "border"); /// assert_eq!(ScaleSize::Zero.class_with("m"), "m-0"); /// assert_eq!(ScaleSize::Three.class_with("p"), "p-3"); diff --git a/extensions/pagetop-bootsier/src/theme/attrs/rounded.rs b/extensions/pagetop-bootsier/src/theme/attrs/rounded.rs index 69976142..2a959767 100644 --- a/extensions/pagetop-bootsier/src/theme/attrs/rounded.rs +++ b/extensions/pagetop-bootsier/src/theme/attrs/rounded.rs @@ -71,7 +71,7 @@ impl RoundedRadius { /// # Ejemplos /// /// ```rust - /// # use pagetop_bootsier::prelude::*; + /// # use pagetop_bootsier::theme::*; /// assert_eq!(RoundedRadius::Scale2.class_with(""), "rounded-2"); /// assert_eq!(RoundedRadius::Zero.class_with("rounded-top"), "rounded-top-0"); /// assert_eq!(RoundedRadius::Scale3.class_with("rounded-top-end"), "rounded-top-end-3"); @@ -103,7 +103,7 @@ impl RoundedRadius { /// # Ejemplos /// /// ```rust - /// # use pagetop_bootsier::prelude::*; + /// # use pagetop_bootsier::theme::*; /// assert_eq!(RoundedRadius::Default.to_class(), "rounded"); /// assert_eq!(RoundedRadius::Zero.to_class(), "rounded-0"); /// assert_eq!(RoundedRadius::Scale3.to_class(), "rounded-3"); diff --git a/extensions/pagetop-bootsier/src/theme/button.rs b/extensions/pagetop-bootsier/src/theme/button.rs index 48eb7283..e494c1df 100644 --- a/extensions/pagetop-bootsier/src/theme/button.rs +++ b/extensions/pagetop-bootsier/src/theme/button.rs @@ -19,8 +19,9 @@ use crate::theme::{ButtonAction, ButtonColor, ButtonSize}; /// # Ejemplo /// /// ```rust -/// # use pagetop::prelude::*; -/// # use pagetop_bootsier::prelude::*; +/// use pagetop::prelude::*; +/// use pagetop_bootsier::theme::*; +/// /// let save = Button::submit(L10n::n("Save")) /// .with_color(ButtonColor::Background(Color::Primary)); /// diff --git a/extensions/pagetop-bootsier/src/theme/classes/border.rs b/extensions/pagetop-bootsier/src/theme/classes/border.rs index 8a6bd6c4..bfa65522 100644 --- a/extensions/pagetop-bootsier/src/theme/classes/border.rs +++ b/extensions/pagetop-bootsier/src/theme/classes/border.rs @@ -26,45 +26,33 @@ use crate::theme::attrs::{BorderColor, Opacity, ScaleSize, Side}; /// /// # Ejemplos /// -/// **Borde global:** /// ```rust -/// # use pagetop_bootsier::prelude::*; +/// use pagetop_bootsier::theme::*; +/// +/// // Borde global. /// let b = classes::Border::with(ScaleSize::Two); /// assert_eq!(b.to_class(), "border-2"); -/// ``` /// -/// **Aditivo (solo borde superior):** -/// ```rust -/// # use pagetop_bootsier::prelude::*; +/// // Aditivo (sólo borde superior): /// let b = classes::Border::default().with_side(Side::Top, ScaleSize::One); /// assert_eq!(b.to_class(), "border-top-1"); -/// ``` /// -/// **Sustractivo (borde global menos el superior):** -/// ```rust -/// # use pagetop_bootsier::prelude::*; +/// // Sustractivo (borde global menos el superior): /// let b = classes::Border::new().with_side(Side::Top, ScaleSize::Zero); /// assert_eq!(b.to_class(), "border border-top-0"); -/// ``` /// -/// **Ancho por lado (lado lógico inicial a 2 y final a 4):** -/// ```rust -/// # use pagetop_bootsier::prelude::*; +/// // Ancho por lado (lado lógico inicial a 2 y final a 4): /// let b = classes::Border::default() /// .with_side(Side::Start, ScaleSize::Two) /// .with_side(Side::End, ScaleSize::Four); /// assert_eq!(b.to_class(), "border-end-4 border-start-2"); -/// ``` /// -/// **Combinado (ejemplo completo):** -/// ```rust -/// # use pagetop_bootsier::prelude::*; +/// // Combinado (ejemplo completo): /// let b = classes::Border::new() // Borde por defecto. /// .with_side(Side::Top, ScaleSize::Zero) // Quita borde superior. /// .with_side(Side::End, ScaleSize::Three) // Ancho 3 para el lado lógico final. /// .with_color(BorderColor::Theme(Color::Primary)) /// .with_opacity(Opacity::Half); -/// /// assert_eq!(b.to_class(), "border border-top-0 border-end-3 border-primary border-opacity-50"); /// ``` #[rustfmt::skip] @@ -158,7 +146,7 @@ impl Border { /// # Ejemplos /// /// ```rust -/// # use pagetop_bootsier::prelude::*; +/// # use pagetop_bootsier::theme::*; /// // Convertir explícitamente con `From::from`: /// let b = classes::Border::from(ScaleSize::Two); /// assert_eq!(b.to_class(), "border-2"); diff --git a/extensions/pagetop-bootsier/src/theme/classes/color.rs b/extensions/pagetop-bootsier/src/theme/classes/color.rs index 4776ca9f..10638a52 100644 --- a/extensions/pagetop-bootsier/src/theme/classes/color.rs +++ b/extensions/pagetop-bootsier/src/theme/classes/color.rs @@ -9,7 +9,8 @@ use crate::theme::attrs::{ColorBg, ColorText, Opacity}; /// # Ejemplos /// /// ``` -/// # use pagetop_bootsier::prelude::*; +/// use pagetop_bootsier::theme::*; +/// /// // Sin clases. /// let s = classes::Background::new(); /// assert_eq!(s.to_class(), ""); @@ -90,7 +91,7 @@ impl From<(ColorBg, Opacity)> for Background { /// # Ejemplo /// /// ``` - /// # use pagetop_bootsier::prelude::*; + /// # use pagetop_bootsier::theme::*; /// let s: classes::Background = (ColorBg::White, Opacity::SemiTransparent).into(); /// assert_eq!(s.to_class(), "bg-white bg-opacity-25"); /// ``` @@ -105,7 +106,7 @@ impl From for Background { /// # Ejemplo /// /// ``` - /// # use pagetop_bootsier::prelude::*; + /// # use pagetop_bootsier::theme::*; /// let s: classes::Background = ColorBg::Black.into(); /// assert_eq!(s.to_class(), "bg-black"); /// ``` @@ -121,7 +122,8 @@ impl From for Background { /// # Ejemplos /// /// ``` -/// # use pagetop_bootsier::prelude::*; +/// use pagetop_bootsier::theme::*; +/// /// // Sin clases. /// let s = classes::Text::new(); /// assert_eq!(s.to_class(), ""); @@ -202,7 +204,7 @@ impl From<(ColorText, Opacity)> for Text { /// # Ejemplo /// /// ``` - /// # use pagetop_bootsier::prelude::*; + /// # use pagetop_bootsier::theme::*; /// let s: classes::Text = (ColorText::Theme(Color::Danger), Opacity::Opaque).into(); /// assert_eq!(s.to_class(), "text-danger text-opacity-100"); /// ``` @@ -218,7 +220,7 @@ impl From for Text { /// # Ejemplo /// /// ``` - /// # use pagetop_bootsier::prelude::*; + /// # use pagetop_bootsier::theme::*; /// let s: classes::Text = ColorText::Black.into(); /// assert_eq!(s.to_class(), "text-black"); /// ``` diff --git a/extensions/pagetop-bootsier/src/theme/classes/layout.rs b/extensions/pagetop-bootsier/src/theme/classes/layout.rs index 2a927acc..ee403a88 100644 --- a/extensions/pagetop-bootsier/src/theme/classes/layout.rs +++ b/extensions/pagetop-bootsier/src/theme/classes/layout.rs @@ -10,7 +10,8 @@ use crate::theme::BreakPoint; /// # Ejemplos /// /// ```rust -/// # use pagetop_bootsier::prelude::*; +/// use pagetop_bootsier::theme::*; +/// /// let m = classes::Margin::with(Side::Top, ScaleSize::Three); /// assert_eq!(m.to_class(), "mt-3"); /// @@ -97,7 +98,8 @@ impl Margin { /// # Ejemplos /// /// ```rust -/// # use pagetop_bootsier::prelude::*; +/// use pagetop_bootsier::theme::*; +/// /// let p = classes::Padding::with(Side::LeftAndRight, ScaleSize::Two); /// assert_eq!(p.to_class(), "px-2"); /// diff --git a/extensions/pagetop-bootsier/src/theme/classes/rounded.rs b/extensions/pagetop-bootsier/src/theme/classes/rounded.rs index 16213e01..9fcb544e 100644 --- a/extensions/pagetop-bootsier/src/theme/classes/rounded.rs +++ b/extensions/pagetop-bootsier/src/theme/classes/rounded.rs @@ -14,42 +14,30 @@ use crate::theme::attrs::RoundedRadius; /// /// # Ejemplos /// -/// **Radio global:** /// ```rust -/// # use pagetop_bootsier::prelude::*; +/// use pagetop_bootsier::theme::*; +/// +/// // Radio global: /// let r = classes::Rounded::with(RoundedRadius::Default); /// assert_eq!(r.to_class(), "rounded"); -/// ``` /// -/// **Sin redondeo:** -/// ```rust -/// # use pagetop_bootsier::prelude::*; +/// // Sin redondeo: /// let r = classes::Rounded::new(); /// assert_eq!(r.to_class(), ""); -/// ``` /// -/// **Radio en las esquinas de un lado lógico:** -/// ```rust -/// # use pagetop_bootsier::prelude::*; +/// // Radio en las esquinas de un lado lógico: /// let r = classes::Rounded::new().with_end(RoundedRadius::Scale2); /// assert_eq!(r.to_class(), "rounded-end-2"); -/// ``` /// -/// **Radio en una esquina concreta:** -/// ```rust -/// # use pagetop_bootsier::prelude::*; +/// // Radio en una esquina concreta: /// let r = classes::Rounded::new().with_top_start(RoundedRadius::Scale3); /// assert_eq!(r.to_class(), "rounded-top-start-3"); -/// ``` /// -/// **Combinado (ejemplo completo):** -/// ```rust -/// # use pagetop_bootsier::prelude::*; +/// // Combinado (ejemplo completo): /// let r = classes::Rounded::new() /// .with_top(RoundedRadius::Default) // Añade redondeo arriba. /// .with_bottom_start(RoundedRadius::Scale4) // Añade una esquina redondeada concreta. /// .with_bottom_end(RoundedRadius::Circle); // Añade redondeo extremo en otra esquina. -/// /// assert_eq!(r.to_class(), "rounded-top rounded-bottom-start-4 rounded-bottom-end-circle"); /// ``` #[rustfmt::skip] diff --git a/extensions/pagetop-bootsier/src/theme/container.rs b/extensions/pagetop-bootsier/src/theme/container.rs index a860110f..707fdc4f 100644 --- a/extensions/pagetop-bootsier/src/theme/container.rs +++ b/extensions/pagetop-bootsier/src/theme/container.rs @@ -6,16 +6,6 @@ //! Con [`container::Width`](crate::theme::container::Width) se puede definir el ancho y el //! comportamiento *responsive* del contenedor. También permite aplicar utilidades de estilo para el //! fondo, texto, borde o esquinas redondeadas. -//! -//! # Ejemplo -//! -//! ```rust -//! # use pagetop::prelude::*; -//! # use pagetop_bootsier::prelude::*; -//! let main = Container::main() -//! .with_id("main-page") -//! .with_width(container::Width::From(BreakPoint::LG)); -//! ``` mod props; pub use props::{Kind, Width}; diff --git a/extensions/pagetop-bootsier/src/theme/container/component.rs b/extensions/pagetop-bootsier/src/theme/container/component.rs index 0635ad18..9e4e33c0 100644 --- a/extensions/pagetop-bootsier/src/theme/container/component.rs +++ b/extensions/pagetop-bootsier/src/theme/container/component.rs @@ -1,11 +1,23 @@ use pagetop::prelude::*; -use crate::prelude::*; +use crate::theme::*; -/// Componente para crear un **contenedor de componentes**. +/// Componente para crear un **contenedor de componentes** ([`container`]). /// -/// Envuelve un contenido con la etiqueta HTML indicada por [`container::Kind`]. Sólo se renderiza -/// si existen componentes hijos (*children*). +/// Envuelve un conjunto de componentes en un contenedor establecido que se crea aplicando uno de +/// los tipos definidos en [`container::Kind`]. +/// +/// Si no contiene elementos, el componente **no se renderiza**. +/// +/// # Ejemplo +/// +/// ```rust +/// use pagetop_bootsier::theme::*; +/// +/// let main = Container::main() +/// .with_id("main-page") +/// .with_width(container::Width::From(BreakPoint::LG)); +/// ``` #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Container { #[getters(skip)] diff --git a/extensions/pagetop-bootsier/src/theme/dropdown.rs b/extensions/pagetop-bootsier/src/theme/dropdown.rs index 213756c7..c8e27870 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown.rs @@ -1,4 +1,4 @@ -//! Definiciones para crear menús desplegables [`Dropdown`]. +//! Definiciones para crear menús desplegables ([`Dropdown`]). //! //! Cada [`dropdown::Item`](crate::theme::dropdown::Item) representa un elemento individual del //! desplegable [`Dropdown`], con distintos comportamientos según su finalidad, como enlaces de @@ -6,23 +6,6 @@ //! //! Los ítems pueden estar activos, deshabilitados o abrirse en nueva ventana según su contexto y //! configuración, y permiten incluir etiquetas localizables usando [`L10n`](pagetop::locale::L10n). -//! -//! # Ejemplo -//! -//! ```rust -//! # use pagetop::prelude::*; -//! # use pagetop_bootsier::prelude::*; -//! let dd = Dropdown::new() -//! .with_title(L10n::n("Menu")) -//! .with_button_color(ButtonColor::Background(Color::Secondary)) -//! .with_auto_close(dropdown::AutoClose::ClickableInside) -//! .with_direction(dropdown::Direction::Dropend) -//! .with_item(dropdown::Item::link(L10n::n("Home"), |_| "/".into())) -//! .with_item(dropdown::Item::link_blank(L10n::n("External"), |_| "https://docs.rs".into())) -//! .with_item(dropdown::Item::divider()) -//! .with_item(dropdown::Item::header(L10n::n("User session"))) -//! .with_item(dropdown::Item::button(L10n::n("Sign out"))); -//! ``` mod props; pub use props::{AutoClose, Direction, MenuAlign, MenuPosition}; diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs index 833cf40b..ca15c635 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs @@ -1,14 +1,14 @@ use pagetop::prelude::*; -use crate::prelude::*; +use crate::theme::*; use crate::LOCALES_BOOTSIER; -/// Componente para crear un **menú desplegable**. +/// Componente para crear un **menú desplegable** ([`dropdown`]). /// /// Renderiza un botón (único o desdoblado, ver [`with_button_split()`](Self::with_button_split)) -/// para mostrar un menú desplegable de elementos [`dropdown::Item`], que se muestra/oculta según la -/// interacción del usuario. Admite variaciones de tamaño/color del botón, también dirección de -/// apertura, alineación o política de cierre. +/// para mostrar un menú desplegable de elementos [`dropdown::Item`], que se muestra u oculta según +/// la interacción del usuario. Admite variaciones para el tamaño y el color del botón, también para +/// la dirección de apertura, alineación o política de cierre. /// /// Si no tiene título (ver [`with_title()`](Self::with_title)) se muestra únicamente la lista de /// elementos sin ningún botón para interactuar. @@ -17,8 +17,25 @@ use crate::LOCALES_BOOTSIER; /// cuenta **el título** (si no existe le asigna uno por defecto) y **la lista de elementos**; el /// resto de propiedades no afectarán a su representación en [`Nav`]. /// -/// Ver ejemplo en el módulo [`dropdown`]. /// Si no contiene elementos, el componente **no se renderiza**. +/// +/// # Ejemplo +/// +/// ```rust +/// use pagetop::prelude::*; +/// use pagetop_bootsier::theme::*; +/// +/// let dd = Dropdown::new() +/// .with_title(L10n::n("Menu")) +/// .with_button_color(ButtonColor::Background(Color::Secondary)) +/// .with_auto_close(dropdown::AutoClose::ClickableInside) +/// .with_direction(dropdown::Direction::Dropend) +/// .with_item(dropdown::Item::link(L10n::n("Home"), |_| "/".into())) +/// .with_item(dropdown::Item::link_blank(L10n::n("External"), |_| "https://docs.rs".into())) +/// .with_item(dropdown::Item::divider()) +/// .with_item(dropdown::Item::header(L10n::n("User session"))) +/// .with_item(dropdown::Item::button(L10n::n("Sign out"))); +/// ``` #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Dropdown { #[getters(skip)] diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/props.rs b/extensions/pagetop-bootsier/src/theme/dropdown/props.rs index fd315508..fb11783d 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/props.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/props.rs @@ -1,6 +1,6 @@ use pagetop::prelude::*; -use crate::prelude::*; +use crate::theme::*; // **< AutoClose >********************************************************************************** diff --git a/extensions/pagetop-bootsier/src/theme/form.rs b/extensions/pagetop-bootsier/src/theme/form.rs index 634dc48a..82c603ef 100644 --- a/extensions/pagetop-bootsier/src/theme/form.rs +++ b/extensions/pagetop-bootsier/src/theme/form.rs @@ -1,36 +1,4 @@ //! Definiciones para crear formularios ([`Form`]). -//! -//! # Ejemplo -//! -//! ```rust -//! use pagetop::prelude::*; -//! use pagetop_bootsier::prelude::*; -//! -//! let form_login = Form::new() -//! .with_id("login") -//! .with_action("/login") -//! .with_child( -//! form::input::Field::email() -//! .with_name("email") -//! .with_label(L10n::n("Email")) -//! .with_required(true), -//! ) -//! .with_child( -//! form::input::Field::password() -//! .with_name("password") -//! .with_label(L10n::n("Password")) -//! .with_required(true), -//! ) -//! .with_child( -//! form::Checkbox::check() -//! .with_name("remember") -//! .with_label(L10n::n("Remember me")), -//! ) -//! .with_child( -//! Button::submit(L10n::n("Sign in")) -//! .with_color(ButtonColor::Background(Color::Primary)), -//! ); -//! ``` mod props; pub use props::{Autocomplete, AutofillField, CheckboxKind, Method}; diff --git a/extensions/pagetop-bootsier/src/theme/form/check.rs b/extensions/pagetop-bootsier/src/theme/form/check.rs index 824ed92a..434b7e6c 100644 --- a/extensions/pagetop-bootsier/src/theme/form/check.rs +++ b/extensions/pagetop-bootsier/src/theme/form/check.rs @@ -17,7 +17,7 @@ use pagetop::prelude::*; /// /// ```rust /// # use pagetop::prelude::*; -/// # use pagetop_bootsier::prelude::*; +/// # use pagetop_bootsier::theme::*; /// let item = form::check::Item::new("apple", L10n::n("Apple")).with_checked(true); /// ``` #[derive(AutoDefault, Clone, Debug, Getters)] @@ -82,7 +82,7 @@ impl Item { /// /// ```rust /// # use pagetop::prelude::*; -/// # use pagetop_bootsier::prelude::*; +/// # use pagetop_bootsier::theme::*; /// let interests = form::check::Field::new() /// .with_name("interests") /// .with_label(L10n::n("Areas of interest")) diff --git a/extensions/pagetop-bootsier/src/theme/form/checkbox.rs b/extensions/pagetop-bootsier/src/theme/form/checkbox.rs index 6206d983..60d7120d 100644 --- a/extensions/pagetop-bootsier/src/theme/form/checkbox.rs +++ b/extensions/pagetop-bootsier/src/theme/form/checkbox.rs @@ -17,7 +17,7 @@ use crate::LOCALES_BOOTSIER; /// /// ```rust /// # use pagetop::prelude::*; -/// # use pagetop_bootsier::prelude::*; +/// # use pagetop_bootsier::theme::*; /// let accept_terms = form::Checkbox::check() // También sirve new() o default(). /// .with_name("terms_accepted") /// .with_label(L10n::n("I accept the terms and conditions")) diff --git a/extensions/pagetop-bootsier/src/theme/form/component.rs b/extensions/pagetop-bootsier/src/theme/form/component.rs index e4c22a56..80efa941 100644 --- a/extensions/pagetop-bootsier/src/theme/form/component.rs +++ b/extensions/pagetop-bootsier/src/theme/form/component.rs @@ -2,9 +2,9 @@ use pagetop::prelude::*; use crate::theme::form; -/// Componente para crear un **formulario**. +/// Componente para crear un **formulario** ([`form`]). /// -/// Este componente renderiza un `
` estándar con soporte para los atributos más habituales: +/// Este componente renderiza un formulario estándar con soporte para los atributos más habituales: /// /// - `id`: identificador opcional del formulario. /// - `classes`: clases CSS adicionales (p. ej. utilidades CSS). @@ -17,13 +17,33 @@ use crate::theme::form; /// # Ejemplo /// /// ```rust -/// # use pagetop::prelude::*; -/// # use pagetop_bootsier::prelude::*; -/// let search = Form::new() -/// .with_id("search") -/// .with_action("/search") -/// .with_method(form::Method::Get) -/// .with_child(form::input::Field::search().with_name("q")); +/// use pagetop::prelude::*; +/// use pagetop_bootsier::theme::*; +/// +/// let form_login = Form::new() +/// .with_id("login") +/// .with_action("/login") +/// .with_child( +/// form::input::Field::email() +/// .with_name("email") +/// .with_label(L10n::n("Email")) +/// .with_required(true), +/// ) +/// .with_child( +/// form::input::Field::password() +/// .with_name("password") +/// .with_label(L10n::n("Password")) +/// .with_required(true), +/// ) +/// .with_child( +/// form::Checkbox::check() +/// .with_name("remember") +/// .with_label(L10n::n("Remember me")), +/// ) +/// .with_child( +/// Button::submit(L10n::n("Sign in")) +/// .with_color(ButtonColor::Background(Color::Primary)), +/// ); /// ``` #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Form { diff --git a/extensions/pagetop-bootsier/src/theme/form/fieldset.rs b/extensions/pagetop-bootsier/src/theme/form/fieldset.rs index b5d0e822..1aacba6c 100644 --- a/extensions/pagetop-bootsier/src/theme/form/fieldset.rs +++ b/extensions/pagetop-bootsier/src/theme/form/fieldset.rs @@ -15,7 +15,7 @@ use pagetop::prelude::*; /// /// ```rust /// # use pagetop::prelude::*; -/// # use pagetop_bootsier::prelude::*; +/// # use pagetop_bootsier::theme::*; /// let personal_data = form::Fieldset::new() /// .with_legend(L10n::n("Personal data")) /// .with_description(L10n::n("Enter your full name and contact email.")) diff --git a/extensions/pagetop-bootsier/src/theme/form/hidden.rs b/extensions/pagetop-bootsier/src/theme/form/hidden.rs index 61d24547..6d367155 100644 --- a/extensions/pagetop-bootsier/src/theme/form/hidden.rs +++ b/extensions/pagetop-bootsier/src/theme/form/hidden.rs @@ -12,7 +12,7 @@ use pagetop::prelude::*; /// /// ```rust /// # use pagetop::prelude::*; -/// # use pagetop_bootsier::prelude::*; +/// # use pagetop_bootsier::theme::*; /// let token = form::Hidden::new() /// .with_name("csrf_token") /// .with_value("a1b2c3d4e5"); diff --git a/extensions/pagetop-bootsier/src/theme/form/input.rs b/extensions/pagetop-bootsier/src/theme/form/input.rs index a6dc34ea..997b7c45 100644 --- a/extensions/pagetop-bootsier/src/theme/form/input.rs +++ b/extensions/pagetop-bootsier/src/theme/form/input.rs @@ -106,7 +106,7 @@ impl fmt::Display for Mode { /// /// ```rust /// # use pagetop::prelude::*; -/// # use pagetop_bootsier::prelude::*; +/// # use pagetop_bootsier::theme::*; /// let email = form::input::Field::email() /// .with_name("email") /// .with_label(L10n::n("Email address")) diff --git a/extensions/pagetop-bootsier/src/theme/form/props.rs b/extensions/pagetop-bootsier/src/theme/form/props.rs index dbd1e705..2ef88f3e 100644 --- a/extensions/pagetop-bootsier/src/theme/form/props.rs +++ b/extensions/pagetop-bootsier/src/theme/form/props.rs @@ -52,7 +52,7 @@ pub enum CheckboxKind { /// /// ```rust /// # use pagetop::prelude::*; -/// # use pagetop_bootsier::prelude::*; +/// # use pagetop_bootsier::theme::*; /// // Correo electrónico con sugerencia semántica del navegador. /// let ac = form::Autocomplete::email(); /// @@ -244,7 +244,7 @@ impl fmt::Display for Autocomplete { /// # Ejemplo /// /// ```rust -/// # use pagetop_bootsier::prelude::*; +/// # use pagetop_bootsier::theme::*; /// let ac = form::Autocomplete::token(form::AutofillField::Username); /// let ac = form::Autocomplete::shipping(form::AutofillField::StreetAddress); /// let ac = form::Autocomplete::section("job", form::AutofillField::Email); diff --git a/extensions/pagetop-bootsier/src/theme/form/radio.rs b/extensions/pagetop-bootsier/src/theme/form/radio.rs index 2514c4bf..d79c4f44 100644 --- a/extensions/pagetop-bootsier/src/theme/form/radio.rs +++ b/extensions/pagetop-bootsier/src/theme/form/radio.rs @@ -16,7 +16,7 @@ use crate::LOCALES_BOOTSIER; /// /// ```rust /// # use pagetop::prelude::*; -/// # use pagetop_bootsier::prelude::*; +/// # use pagetop_bootsier::theme::*; /// let item = form::radio::Item::new("monthly", L10n::n("Monthly")).with_checked(true); /// ``` #[derive(AutoDefault, Clone, Debug, Getters)] @@ -76,7 +76,7 @@ impl Item { /// /// ```rust /// # use pagetop::prelude::*; -/// # use pagetop_bootsier::prelude::*; +/// # use pagetop_bootsier::theme::*; /// let plan = form::radio::Field::new() /// .with_name("plan") /// .with_label(L10n::n("Subscription plan")) diff --git a/extensions/pagetop-bootsier/src/theme/form/range.rs b/extensions/pagetop-bootsier/src/theme/form/range.rs index 40350479..45d07acf 100644 --- a/extensions/pagetop-bootsier/src/theme/form/range.rs +++ b/extensions/pagetop-bootsier/src/theme/form/range.rs @@ -10,7 +10,7 @@ use pagetop::prelude::*; /// /// ```rust /// # use pagetop::prelude::*; -/// # use pagetop_bootsier::prelude::*; +/// # use pagetop_bootsier::theme::*; /// let volume = form::Range::new() /// .with_name("volume") /// .with_label(L10n::n("Volume")) diff --git a/extensions/pagetop-bootsier/src/theme/form/select.rs b/extensions/pagetop-bootsier/src/theme/form/select.rs index 1fa74f09..92736586 100644 --- a/extensions/pagetop-bootsier/src/theme/form/select.rs +++ b/extensions/pagetop-bootsier/src/theme/form/select.rs @@ -20,7 +20,7 @@ use crate::LOCALES_BOOTSIER; /// /// ```rust /// # use pagetop::prelude::*; -/// # use pagetop_bootsier::prelude::*; +/// # use pagetop_bootsier::theme::*; /// let item = form::select::Item::new("es", L10n::n("Spanish")).with_selected(true); /// ``` #[derive(AutoDefault, Clone, Debug, Getters)] @@ -76,7 +76,7 @@ impl Item { /// /// ```rust /// # use pagetop::prelude::*; -/// # use pagetop_bootsier::prelude::*; +/// # use pagetop_bootsier::theme::*; /// let group = form::select::Group::new(L10n::n("Europe")) /// .with_item(form::select::Item::new("es", L10n::n("Spanish"))) /// .with_item(form::select::Item::new("fr", L10n::n("French"))); @@ -149,7 +149,7 @@ pub enum Entry { /// /// ```rust /// # use pagetop::prelude::*; -/// # use pagetop_bootsier::prelude::*; +/// # use pagetop_bootsier::theme::*; /// let idioma = form::select::Field::new() /// .with_name("language") /// .with_label(L10n::n("Language")) diff --git a/extensions/pagetop-bootsier/src/theme/form/textarea.rs b/extensions/pagetop-bootsier/src/theme/form/textarea.rs index c545df8c..781e1d09 100644 --- a/extensions/pagetop-bootsier/src/theme/form/textarea.rs +++ b/extensions/pagetop-bootsier/src/theme/form/textarea.rs @@ -13,7 +13,7 @@ use crate::LOCALES_BOOTSIER; /// /// ```rust /// # use pagetop::prelude::*; -/// # use pagetop_bootsier::prelude::*; +/// # use pagetop_bootsier::theme::*; /// let descripcion = form::Textarea::new() /// .with_name("description") /// .with_label(L10n::n("Description")) diff --git a/extensions/pagetop-bootsier/src/theme/icon.rs b/extensions/pagetop-bootsier/src/theme/icon.rs index 935aa847..9ef6aeee 100644 --- a/extensions/pagetop-bootsier/src/theme/icon.rs +++ b/extensions/pagetop-bootsier/src/theme/icon.rs @@ -1,4 +1,4 @@ -use crate::prelude::*; +use crate::theme::*; const DEFAULT_VIEWBOX: &str = "0 0 16 16"; diff --git a/extensions/pagetop-bootsier/src/theme/image/component.rs b/extensions/pagetop-bootsier/src/theme/image/component.rs index 898fb573..678ccdb3 100644 --- a/extensions/pagetop-bootsier/src/theme/image/component.rs +++ b/extensions/pagetop-bootsier/src/theme/image/component.rs @@ -1,14 +1,16 @@ use pagetop::prelude::*; -use crate::prelude::*; +use crate::theme::*; -/// Componente para renderizar una **imagen**. +/// Componente para renderizar una **imagen** ([`image`]). /// -/// - Ajusta su disposición según el origen definido en [`image::Source`]. -/// - Permite configurar **dimensiones** ([`with_size()`](Self::with_size)), **borde** +/// A una imagen se le puede: +/// +/// - Establecer su contenido a partir del origen definido en [`image::Source`]. +/// - Configurar sus **dimensiones** ([`with_size()`](Self::with_size)), **borde** /// ([`classes::Border`](crate::theme::classes::Border)) y **redondeo de esquinas** /// ([`classes::Rounded`](crate::theme::classes::Rounded)). -/// - Resuelve el texto alternativo `alt` con **localización** mediante [`L10n`]. +/// - Aplicar el texto alternativo `alt` con **localización** mediante [`L10n`]. #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Image { #[getters(skip)] diff --git a/extensions/pagetop-bootsier/src/theme/nav.rs b/extensions/pagetop-bootsier/src/theme/nav.rs index 16b3d2d7..74e9214d 100644 --- a/extensions/pagetop-bootsier/src/theme/nav.rs +++ b/extensions/pagetop-bootsier/src/theme/nav.rs @@ -1,4 +1,4 @@ -//! Definiciones para crear menús [`Nav`] o alguna de sus variantes de presentación. +//! Definiciones para crear menús ([`Nav`]). //! //! Cada [`nav::Item`](crate::theme::nav::Item) representa un elemento individual del menú [`Nav`], //! con distintos comportamientos según su finalidad, como enlaces de navegación o menús @@ -6,26 +6,6 @@ //! //! Los ítems pueden estar activos, deshabilitados o abrirse en nueva ventana según su contexto y //! configuración, y permiten incluir etiquetas localizables usando [`L10n`](pagetop::locale::L10n). -//! -//! # Ejemplo -//! -//! ```rust -//! # use pagetop::prelude::*; -//! # use pagetop_bootsier::prelude::*; -//! let nav = Nav::tabs() -//! .with_layout(nav::Layout::End) -//! .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) -//! .with_item(nav::Item::link_blank(L10n::n("External"), |_| "https://docs.rs".into())) -//! .with_item(nav::Item::dropdown( -//! Dropdown::new() -//! .with_title(L10n::n("Options")) -//! .with_item(ChildOp::AddMany(vec![ -//! dropdown::Item::link(L10n::n("Action"), |_| "/action".into()).into(), -//! dropdown::Item::link(L10n::n("Another"), |_| "/another".into()).into(), -//! ])), -//! )) -//! .with_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#".into())); -//! ``` mod props; pub use props::{Kind, Layout}; diff --git a/extensions/pagetop-bootsier/src/theme/nav/component.rs b/extensions/pagetop-bootsier/src/theme/nav/component.rs index 0b7797c8..aeb0447e 100644 --- a/extensions/pagetop-bootsier/src/theme/nav/component.rs +++ b/extensions/pagetop-bootsier/src/theme/nav/component.rs @@ -1,15 +1,35 @@ use pagetop::prelude::*; -use crate::prelude::*; +use crate::theme::*; -/// Componente para crear un **menú** o alguna de sus variantes ([`nav::Kind`]). +/// Componente para crear un **menú** ([`nav`]). /// /// Presenta un menú con una lista de elementos usando una vista básica, o alguna de sus variantes -/// como *pestañas* (`Tabs`), *botones* (`Pills`) o *subrayado* (`Underline`). También permite -/// controlar su distribución y orientación ([`nav::Layout`](crate::theme::nav::Layout)). +/// ([`nav::Kind`]) como *pestañas* (`Tabs`), *botones* (`Pills`) o *subrayado* (`Underline`). +/// También permite controlar su distribución y orientación ([`nav::Layout`](crate::theme::nav::Layout)). /// -/// Ver ejemplo en el módulo [`nav`]. /// Si no contiene elementos, el componente **no se renderiza**. +/// +/// # Ejemplo +/// +/// ```rust +/// use pagetop::prelude::*; +/// use pagetop_bootsier::theme::*; +/// +/// let nav = Nav::tabs() +/// .with_layout(nav::Layout::End) +/// .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) +/// .with_item(nav::Item::link_blank(L10n::n("External"), |_| "https://docs.rs".into())) +/// .with_item(nav::Item::dropdown( +/// Dropdown::new() +/// .with_title(L10n::n("Options")) +/// .with_item(ChildOp::AddMany(vec![ +/// dropdown::Item::link(L10n::n("Action"), |_| "/action".into()).into(), +/// dropdown::Item::link(L10n::n("Another"), |_| "/another".into()).into(), +/// ])), +/// )) +/// .with_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#".into())); +/// ``` #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Nav { #[getters(skip)] diff --git a/extensions/pagetop-bootsier/src/theme/nav/item.rs b/extensions/pagetop-bootsier/src/theme/nav/item.rs index 956ccdcd..ef5a6fe9 100644 --- a/extensions/pagetop-bootsier/src/theme/nav/item.rs +++ b/extensions/pagetop-bootsier/src/theme/nav/item.rs @@ -1,6 +1,6 @@ use pagetop::prelude::*; -use crate::prelude::*; +use crate::theme::*; use crate::LOCALES_BOOTSIER; // **< ItemKind >*********************************************************************************** diff --git a/extensions/pagetop-bootsier/src/theme/navbar.rs b/extensions/pagetop-bootsier/src/theme/navbar.rs index 31a16ccc..f381b1f3 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar.rs @@ -1,4 +1,4 @@ -//! Definiciones para crear barras de navegación [`Navbar`]. +//! Definiciones para crear barras de navegación ([`Navbar`]). //! //! Cada [`navbar::Item`](crate::theme::navbar::Item) representa un elemento individual de la barra //! de navegación [`Navbar`], con distintos comportamientos según su finalidad, como menús @@ -6,126 +6,6 @@ //! //! También puede mostrar una marca de identidad ([`navbar::Brand`](crate::theme::navbar::Brand)) //! que identifique la compañía, producto o nombre del proyecto asociado a la solución web. -//! -//! # Ejemplos -//! -//! Barra **simple**, sólo con un menú horizontal: -//! -//! ```rust -//! # use pagetop::prelude::*; -//! # use pagetop_bootsier::prelude::*; -//! let navbar = Navbar::simple() -//! .with_item(navbar::Item::nav( -//! Nav::new() -//! .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) -//! .with_item(nav::Item::link(L10n::n("About"), |_| "/about".into())) -//! .with_item(nav::Item::link(L10n::n("Contact"), |_| "/contact".into())) -//! )); -//! ``` -//! -//! Barra **colapsable**, con botón de despliegue y contenido en el desplegable cuando colapsa: -//! -//! ```rust -//! # use pagetop::prelude::*; -//! # use pagetop_bootsier::prelude::*; -//! let navbar = Navbar::simple_toggle() -//! .with_expand(BreakPoint::MD) -//! .with_item(navbar::Item::nav( -//! Nav::new() -//! .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) -//! .with_item(nav::Item::link_blank(L10n::n("Docs"), |_| "https://docs.rs".into())) -//! .with_item(nav::Item::link(L10n::n("Support"), |_| "/support".into())) -//! )); -//! ``` -//! -//! Barra con **marca de identidad a la izquierda** y menú a la derecha, típica de una cabecera: -//! -//! ```rust -//! # use pagetop::prelude::*; -//! # use pagetop_bootsier::prelude::*; -//! let brand = navbar::Brand::new() -//! .with_title(L10n::n("PageTop")) -//! .with_route(Some(|cx| cx.route("/"))); -//! -//! let navbar = Navbar::brand_left(brand) -//! .with_item(navbar::Item::nav( -//! Nav::new() -//! .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) -//! .with_item(nav::Item::dropdown( -//! Dropdown::new() -//! .with_title(L10n::n("Tools")) -//! .with_item(dropdown::Item::link( -//! L10n::n("Generator"), |_| "/tools/gen".into()) -//! ) -//! .with_item(dropdown::Item::link( -//! L10n::n("Reports"), |_| "/tools/reports".into()) -//! ) -//! )) -//! .with_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#".into())) -//! )); -//! ``` -//! -//! Barra con **botón de despliegue a la izquierda** y **marca de identidad a la derecha**: -//! -//! ```rust -//! # use pagetop::prelude::*; -//! # use pagetop_bootsier::prelude::*; -//! let brand = navbar::Brand::new() -//! .with_title(L10n::n("Intranet")) -//! .with_route(Some(|cx| cx.route("/"))); -//! -//! let navbar = Navbar::brand_right(brand) -//! .with_expand(BreakPoint::LG) -//! .with_item(navbar::Item::nav( -//! Nav::pills() -//! .with_item(nav::Item::link(L10n::n("Dashboard"), |_| "/dashboard".into())) -//! .with_item(nav::Item::link(L10n::n("Users"), |_| "/users".into())) -//! )); -//! ``` -//! -//! Barra con el **contenido en un *offcanvas***, ideal para dispositivos móviles o menús largos: -//! -//! ```rust -//! # use pagetop::prelude::*; -//! # use pagetop_bootsier::prelude::*; -//! let oc = Offcanvas::new() -//! .with_id("main_offcanvas") -//! .with_title(L10n::n("Main menu")) -//! .with_placement(offcanvas::Placement::Start) -//! .with_backdrop(offcanvas::Backdrop::Enabled); -//! -//! let navbar = Navbar::offcanvas(oc) -//! .with_item(navbar::Item::nav( -//! Nav::new() -//! .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) -//! .with_item(nav::Item::link(L10n::n("Profile"), |_| "/profile".into())) -//! .with_item(nav::Item::dropdown( -//! Dropdown::new() -//! .with_title(L10n::n("More")) -//! .with_item(dropdown::Item::link(L10n::n("Settings"), |_| "/settings".into())) -//! .with_item(dropdown::Item::link(L10n::n("Help"), |_| "/help".into())) -//! )) -//! )); -//! ``` -//! -//! Barra **fija arriba**: -//! -//! ```rust -//! # use pagetop::prelude::*; -//! # use pagetop_bootsier::prelude::*; -//! let brand = navbar::Brand::new() -//! .with_title(L10n::n("Main App")) -//! .with_route(Some(|cx| cx.route("/"))); -//! -//! let navbar = Navbar::brand_left(brand) -//! .with_position(navbar::Position::FixedTop) -//! .with_item(navbar::Item::nav( -//! Nav::new() -//! .with_item(nav::Item::link(L10n::n("Dashboard"), |_| "/".into())) -//! .with_item(nav::Item::link(L10n::n("Donors"), |_| "/donors".into())) -//! .with_item(nav::Item::link(L10n::n("Stock"), |_| "/stock".into())) -//! )); -//! ``` mod props; pub use props::{Layout, Position}; diff --git a/extensions/pagetop-bootsier/src/theme/navbar/brand.rs b/extensions/pagetop-bootsier/src/theme/navbar/brand.rs index 4d575e03..9e5082b6 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/brand.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/brand.rs @@ -1,6 +1,6 @@ use pagetop::prelude::*; -use crate::prelude::*; +use crate::theme::*; /// Marca de identidad para mostrar en una barra de navegación [`Navbar`]. /// diff --git a/extensions/pagetop-bootsier/src/theme/navbar/component.rs b/extensions/pagetop-bootsier/src/theme/navbar/component.rs index 33aa80de..ccd97e90 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/component.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/component.rs @@ -1,19 +1,139 @@ use pagetop::prelude::*; -use crate::prelude::*; +use crate::theme::*; use crate::LOCALES_BOOTSIER; const TOGGLE_COLLAPSE: &str = "collapse"; const TOGGLE_OFFCANVAS: &str = "offcanvas"; -/// Componente para crear una **barra de navegación**. +/// Componente para crear una **barra de navegación** ([`navbar`]). /// /// Permite mostrar enlaces, menús y una marca de identidad en distintas disposiciones (simples, con /// botón de despliegue o dentro de un [`offcanvas`]), controladas por [`navbar::Layout`]. También /// puede fijarse en la parte superior o inferior del documento mediante [`navbar::Position`]. /// -/// Ver ejemplos en el módulo [`navbar`]. /// Si no contiene elementos, el componente **no se renderiza**. +/// +/// # Ejemplos +/// +/// Barra **simple**, sólo con un menú horizontal: +/// +/// ```rust +/// use pagetop::prelude::*; +/// use pagetop_bootsier::theme::*; +/// +/// let navbar = Navbar::simple() +/// .with_item(navbar::Item::nav( +/// Nav::new() +/// .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) +/// .with_item(nav::Item::link(L10n::n("About"), |_| "/about".into())) +/// .with_item(nav::Item::link(L10n::n("Contact"), |_| "/contact".into())) +/// )); +/// ``` +/// +/// Barra **colapsable**, con botón de despliegue y contenido en el desplegable cuando colapsa: +/// +/// ```rust +/// # use pagetop::prelude::*; +/// # use pagetop_bootsier::theme::*; +/// let navbar = Navbar::simple_toggle() +/// .with_expand(BreakPoint::MD) +/// .with_item(navbar::Item::nav( +/// Nav::new() +/// .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) +/// .with_item(nav::Item::link_blank(L10n::n("Docs"), |_| "https://docs.rs".into())) +/// .with_item(nav::Item::link(L10n::n("Support"), |_| "/support".into())) +/// )); +/// ``` +/// +/// Barra con **marca de identidad a la izquierda** y menú a la derecha, típica de una cabecera: +/// +/// ```rust +/// # use pagetop::prelude::*; +/// # use pagetop_bootsier::theme::*; +/// let brand = navbar::Brand::new() +/// .with_title(L10n::n("PageTop")) +/// .with_route(Some(|cx| cx.route("/"))); +/// +/// let navbar = Navbar::brand_left(brand) +/// .with_item(navbar::Item::nav( +/// Nav::new() +/// .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) +/// .with_item(nav::Item::dropdown( +/// Dropdown::new() +/// .with_title(L10n::n("Tools")) +/// .with_item(dropdown::Item::link( +/// L10n::n("Generator"), |_| "/tools/gen".into()) +/// ) +/// .with_item(dropdown::Item::link( +/// L10n::n("Reports"), |_| "/tools/reports".into()) +/// ) +/// )) +/// .with_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#".into())) +/// )); +/// ``` +/// +/// Barra con **botón de despliegue a la izquierda** y **marca de identidad a la derecha**: +/// +/// ```rust +/// # use pagetop::prelude::*; +/// # use pagetop_bootsier::theme::*; +/// let brand = navbar::Brand::new() +/// .with_title(L10n::n("Intranet")) +/// .with_route(Some(|cx| cx.route("/"))); +/// +/// let navbar = Navbar::brand_right(brand) +/// .with_expand(BreakPoint::LG) +/// .with_item(navbar::Item::nav( +/// Nav::pills() +/// .with_item(nav::Item::link(L10n::n("Dashboard"), |_| "/dashboard".into())) +/// .with_item(nav::Item::link(L10n::n("Users"), |_| "/users".into())) +/// )); +/// ``` +/// +/// Barra con el **contenido en un *offcanvas***, ideal para dispositivos móviles o menús largos: +/// +/// ```rust +/// # use pagetop::prelude::*; +/// # use pagetop_bootsier::theme::*; +/// let oc = Offcanvas::new() +/// .with_id("main_offcanvas") +/// .with_title(L10n::n("Main menu")) +/// .with_placement(offcanvas::Placement::Start) +/// .with_backdrop(offcanvas::Backdrop::Enabled); +/// +/// let navbar = Navbar::offcanvas(oc) +/// .with_item(navbar::Item::nav( +/// Nav::new() +/// .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into())) +/// .with_item(nav::Item::link(L10n::n("Profile"), |_| "/profile".into())) +/// .with_item(nav::Item::dropdown( +/// Dropdown::new() +/// .with_title(L10n::n("More")) +/// .with_item(dropdown::Item::link(L10n::n("Settings"), |_| "/settings".into())) +/// .with_item(dropdown::Item::link(L10n::n("Help"), |_| "/help".into())) +/// )) +/// )); +/// ``` +/// +/// Barra **fija arriba**: +/// +/// ```rust +/// # use pagetop::prelude::*; +/// # use pagetop_bootsier::theme::*; +/// let brand = navbar::Brand::new() +/// .with_title(L10n::n("Main App")) +/// .with_route(Some(|cx| cx.route("/"))); +/// +/// let navbar = Navbar::brand_left(brand) +/// .with_position(navbar::Position::FixedTop) +/// .with_item(navbar::Item::nav( +/// Nav::new() +/// .with_item(nav::Item::link(L10n::n("Dashboard"), |_| "/".into())) +/// .with_item(nav::Item::link(L10n::n("Donors"), |_| "/donors".into())) +/// .with_item(nav::Item::link(L10n::n("Stock"), |_| "/stock".into())) +/// )); +/// ``` #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Navbar { #[getters(skip)] diff --git a/extensions/pagetop-bootsier/src/theme/navbar/item.rs b/extensions/pagetop-bootsier/src/theme/navbar/item.rs index caba4e7d..9b48adf1 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/item.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/item.rs @@ -1,6 +1,6 @@ use pagetop::prelude::*; -use crate::prelude::*; +use crate::theme::*; /// Elementos que puede contener una barra de navegación [`Navbar`](crate::theme::Navbar). /// diff --git a/extensions/pagetop-bootsier/src/theme/navbar/props.rs b/extensions/pagetop-bootsier/src/theme/navbar/props.rs index 1ac248b7..e0d64916 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/props.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/props.rs @@ -1,6 +1,6 @@ use pagetop::prelude::*; -use crate::prelude::*; +use crate::theme::*; // **< Layout >************************************************************************************* diff --git a/extensions/pagetop-bootsier/src/theme/offcanvas.rs b/extensions/pagetop-bootsier/src/theme/offcanvas.rs index 166893aa..63ecbc66 100644 --- a/extensions/pagetop-bootsier/src/theme/offcanvas.rs +++ b/extensions/pagetop-bootsier/src/theme/offcanvas.rs @@ -1,24 +1,4 @@ -//! Definiciones para crear paneles laterales deslizantes [`Offcanvas`]. -//! -//! # Ejemplo -//! -//! ```rust -//! # use pagetop::prelude::*; -//! # use pagetop_bootsier::prelude::*; -//! let panel = Offcanvas::new() -//! .with_id("offcanvas_example") -//! .with_title(L10n::n("Offcanvas title")) -//! .with_placement(offcanvas::Placement::End) -//! .with_backdrop(offcanvas::Backdrop::Enabled) -//! .with_body_scroll(offcanvas::BodyScroll::Enabled) -//! .with_visibility(offcanvas::Visibility::Default) -//! .with_child(Dropdown::new() -//! .with_title(L10n::n("Menu")) -//! .with_item(dropdown::Item::label(L10n::n("Label"))) -//! .with_item(dropdown::Item::link_blank(L10n::n("Docs"), |_| "https://docs.rs".into())) -//! .with_item(dropdown::Item::link(L10n::n("Sign out"), |_| "/signout".into())) -//! ); -//! ``` +//! Definiciones para crear paneles laterales deslizantes ([`Offcanvas`]). mod props; pub use props::{Backdrop, BodyScroll, Placement, Visibility}; diff --git a/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs b/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs index 88aad443..764627e4 100644 --- a/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs +++ b/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs @@ -1,9 +1,9 @@ use pagetop::prelude::*; -use crate::prelude::*; +use crate::theme::*; use crate::LOCALES_BOOTSIER; -/// Componente para crear un **panel lateral deslizante** con contenidos adicionales. +/// Componente para crear un **panel lateral deslizante** ([`offcanvas`]). /// /// Útil para navegación, filtros, formularios o menús contextuales. Incluye las siguientes /// características principales: @@ -19,8 +19,28 @@ use crate::LOCALES_BOOTSIER; /// - Asocia título y controles de accesibilidad a un identificador único y expone atributos /// adecuados para lectores de pantalla y navegación por teclado. /// -/// Ver ejemplo en el módulo [`offcanvas`]. /// Si no contiene elementos, el componente **no se renderiza**. +/// +/// # Ejemplo +/// +/// ```rust +/// use pagetop::prelude::*; +/// use pagetop_bootsier::theme::*; +/// +/// let panel = Offcanvas::new() +/// .with_id("offcanvas_example") +/// .with_title(L10n::n("Offcanvas title")) +/// .with_placement(offcanvas::Placement::End) +/// .with_backdrop(offcanvas::Backdrop::Enabled) +/// .with_body_scroll(offcanvas::BodyScroll::Enabled) +/// .with_visibility(offcanvas::Visibility::Default) +/// .with_child(Dropdown::new() +/// .with_title(L10n::n("Menu")) +/// .with_item(dropdown::Item::label(L10n::n("Label"))) +/// .with_item(dropdown::Item::link_blank(L10n::n("Docs"), |_| "https://docs.rs".into())) +/// .with_item(dropdown::Item::link(L10n::n("Sign out"), |_| "/signout".into())) +/// ); +/// ``` #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Offcanvas { #[getters(skip)] From fa5489dbb004f9ff0c0866e3322d0146c073b3e5 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 10 May 2026 00:38:00 +0200 Subject: [PATCH 08/12] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(seaorm):=20Elimina?= =?UTF-8?q?=20`prelude`=20para=20usar=20`db`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/pagetop-seaorm/README.md | 2 +- extensions/pagetop-seaorm/src/db/migration/schema.rs | 3 ++- extensions/pagetop-seaorm/src/lib.rs | 9 +-------- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/extensions/pagetop-seaorm/README.md b/extensions/pagetop-seaorm/README.md index aa76275a..a687828f 100644 --- a/extensions/pagetop-seaorm/README.md +++ b/extensions/pagetop-seaorm/README.md @@ -68,7 +68,7 @@ async fn main() -> std::io::Result<()> { **Escribe las migraciones** usando la API de SeaORM: ```rust,no_run -use pagetop_seaorm::prelude::*; +use pagetop_seaorm::db::*; #[derive(DeriveMigrationName)] pub struct Migration; diff --git a/extensions/pagetop-seaorm/src/db/migration/schema.rs b/extensions/pagetop-seaorm/src/db/migration/schema.rs index f250eae0..ae6a3c4b 100644 --- a/extensions/pagetop-seaorm/src/db/migration/schema.rs +++ b/extensions/pagetop-seaorm/src/db/migration/schema.rs @@ -52,7 +52,8 @@ //! } //! ``` -use crate::prelude::Iden; +use crate::db::Iden; + use sea_orm::sea_query::{ self, Alias, ColumnDef, ColumnType, Expr, IntoIden, PgInterval, Table, TableCreateStatement, }; diff --git a/extensions/pagetop-seaorm/src/lib.rs b/extensions/pagetop-seaorm/src/lib.rs index 64578cb8..2ba22db7 100644 --- a/extensions/pagetop-seaorm/src/lib.rs +++ b/extensions/pagetop-seaorm/src/lib.rs @@ -69,7 +69,7 @@ async fn main() -> std::io::Result<()> { **Escribe las migraciones** usando la API de SeaORM: ```rust,no_run -use pagetop_seaorm::prelude::*; +use pagetop_seaorm::db::*; #[derive(DeriveMigrationName)] pub struct Migration; @@ -109,13 +109,6 @@ pub mod config; pub mod db; -/// *Prelude* de la extensión. -pub mod prelude { - pub use crate::config::*; - pub use crate::db::*; - pub use crate::install_migrations; -} - /// Implementa la extensión. pub struct SeaORM; From 8c861bff0514f7ca520025513d07c8b9511e9e13 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 10 May 2026 00:43:35 +0200 Subject: [PATCH 09/12] =?UTF-8?q?=F0=9F=93=9D=20(seaorm):=20Corrige=20ejem?= =?UTF-8?q?plos=20de=20documentaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/pagetop-seaorm/src/db/migration/schema.rs | 3 +-- extensions/pagetop-seaorm/src/lib.rs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/extensions/pagetop-seaorm/src/db/migration/schema.rs b/extensions/pagetop-seaorm/src/db/migration/schema.rs index ae6a3c4b..b6f89d3a 100644 --- a/extensions/pagetop-seaorm/src/db/migration/schema.rs +++ b/extensions/pagetop-seaorm/src/db/migration/schema.rs @@ -11,9 +11,8 @@ //! the schema helpers to create the Db fields. //! //! ```rust -//! use sea_orm_migration::{prelude::*, schema::*}; +//! use pagetop_seaorm::db::*; //! -//! #[derive(DeriveMigrationName)] //! pub struct Migration; //! //! #[async_trait::async_trait] diff --git a/extensions/pagetop-seaorm/src/lib.rs b/extensions/pagetop-seaorm/src/lib.rs index 2ba22db7..103d95eb 100644 --- a/extensions/pagetop-seaorm/src/lib.rs +++ b/extensions/pagetop-seaorm/src/lib.rs @@ -43,7 +43,7 @@ Para MySQL o PostgreSQL añade también `db_user`, `db_pass`, `db_host` y `db_po **Declara la extensión** en tu aplicación o en la extensión que la requiera: -```rust,no_run +```rust,ignore use pagetop::prelude::*; struct MyApp; @@ -71,7 +71,6 @@ async fn main() -> std::io::Result<()> { ```rust,no_run use pagetop_seaorm::db::*; -#[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] From aa931ea0527c7325ba7b4f6e1ad01b9aecd0dc68 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Sun, 10 May 2026 21:42:19 +0200 Subject: [PATCH 10/12] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20(seaorm):=20Actualiz?= =?UTF-8?q?a=20`sea-orm`=20a=201.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 353 ++++++------------ extensions/pagetop-seaorm/Cargo.toml | 4 +- extensions/pagetop-seaorm/README.md | 38 +- extensions/pagetop-seaorm/src/db.rs | 12 +- .../src/db/migration/connection.rs | 8 +- .../src/db/migration/manager.rs | 67 ++-- .../src/db/migration/migrator.rs | 66 ++-- .../src/db/migration/prelude.rs | 1 - .../pagetop-seaorm/src/db/migration/schema.rs | 9 +- 9 files changed, 227 insertions(+), 331 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f2541410..8b01826c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,7 +88,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -222,7 +222,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -273,7 +273,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -535,7 +534,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -552,7 +551,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -582,12 +581,6 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -1036,7 +1029,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1047,7 +1040,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1080,7 +1073,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.117", + "syn", ] [[package]] @@ -1102,7 +1095,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.117", + "syn", "unicode-xid", ] @@ -1137,7 +1130,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1146,18 +1139,6 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" -[[package]] -name = "educe" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4bd92664bf78c4d3dba9b7cdafce6fa15b13ed3ed16175218196942e99168a8" -dependencies = [ - "enum-ordinalize", - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "either" version = "1.15.0" @@ -1176,26 +1157,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "enum-ordinalize" -version = "4.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" -dependencies = [ - "enum-ordinalize-derive", -] - -[[package]] -name = "enum-ordinalize-derive" -version = "4.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -1322,7 +1283,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54f0d287c53ffd184d04d8677f590f4ac5379785529e5e08b1c8083acdd5c198" dependencies = [ "memchr", - "thiserror 2.0.18", + "thiserror", ] [[package]] @@ -1335,7 +1296,7 @@ dependencies = [ "ignore", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "unic-langid", ] @@ -1353,7 +1314,7 @@ dependencies = [ "ignore", "intl-memoizer", "log", - "thiserror 2.0.18", + "thiserror", "unic-langid", ] @@ -1499,7 +1460,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1588,7 +1549,7 @@ checksum = "c43d815f896a3c730f0d76b8348a1700dc8d8fd6c377e4590d531bdd646574d8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1692,6 +1653,8 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ + "allocator-api2", + "equivalent", "foldhash", ] @@ -1703,11 +1666,11 @@ checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "hashlink" -version = "0.8.4" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.15.5", ] [[package]] @@ -1715,9 +1678,6 @@ name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -dependencies = [ - "unicode-segmentation", -] [[package]] name = "heck" @@ -1998,7 +1958,7 @@ checksum = "c727f80bfa4a6c6e2508d2f05b6f4bfce242030bd88ed15ae5331c5b5d30fba7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2148,9 +2108,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.27.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", @@ -2251,12 +2211,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -2302,16 +2256,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -2413,7 +2357,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2436,18 +2380,18 @@ dependencies = [ [[package]] name = "ordered-float" -version = "3.9.2" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc" +checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" dependencies = [ "num-traits", ] [[package]] name = "ouroboros" -version = "0.17.2" +version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2ba07320d39dfea882faa70554b4bd342a5f273ed59ba7c1c6b4c840492c954" +checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" dependencies = [ "aliasable", "ouroboros_macro", @@ -2456,15 +2400,15 @@ dependencies = [ [[package]] name = "ouroboros_macro" -version = "0.17.2" +version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec4c6225c69b4ca778c0aea097321a64c421cf4577b331c61b229267edabb6f8" +checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" dependencies = [ "heck 0.4.1", - "proc-macro-error", "proc-macro2", + "proc-macro2-diagnostics", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2533,7 +2477,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2599,12 +2543,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "pastey" version = "0.2.2" @@ -2683,7 +2621,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2712,7 +2650,7 @@ checksum = "a990e22f43e84855daf260dded30524ef4a9021cc7541c26540500a50b624389" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2844,31 +2782,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.117", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", + "syn", ] [[package]] @@ -2890,7 +2804,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -2916,8 +2830,9 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "version_check", + "yansi", ] [[package]] @@ -3178,18 +3093,19 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] name = "sea-orm" -version = "1.0.1" +version = "1.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea1fee0cf8528dbe6eda29d5798afc522a63b75e44c5b15721e6e64af9c7cc4b" +checksum = "2dc312fedd460a47ea563911761d254a84e7b51d8cc73ec92c929e78f33fa957" dependencies = [ "async-stream", "async-trait", - "futures", + "derive_more 2.1.1", + "futures-util", "log", "ouroboros", "sea-orm-macros", @@ -3198,32 +3114,31 @@ dependencies = [ "serde", "sqlx", "strum", - "thiserror 1.0.69", + "thiserror", "tracing", "url", ] [[package]] name = "sea-orm-macros" -version = "1.0.1" +version = "1.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8737b566799ed0444f278d13c300c4c6f1a91782f60ff5825a591852d5502030" +checksum = "9b9a3f90e336ec74803e8eb98c61bc98754c1adfba3b4f84d946237b752b1c88" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2", "quote", "sea-bae", - "syn 2.0.117", + "syn", "unicode-ident", ] [[package]] name = "sea-query" -version = "0.31.1" +version = "0.32.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4fd043b8117af233e221f73e3ea8dfbc8e8c3c928017c474296db45c649105c" +checksum = "8a5d1c518eaf5eda38e5773f902b26ab6d5e9e9e2bb2349ca6c64cf96f80448c" dependencies = [ - "educe", "inherent", "ordered-float", "sea-query-derive", @@ -3231,9 +3146,9 @@ dependencies = [ [[package]] name = "sea-query-binder" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754965d4aee6145bec25d0898e5c931e6c22859789ce62fd85a42a15ed5a8ce3" +checksum = "b0019f47430f7995af63deda77e238c17323359af241233ec768aba1faea7608" dependencies = [ "sea-query", "sqlx", @@ -3249,15 +3164,15 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.117", - "thiserror 2.0.18", + "syn", + "thiserror", ] [[package]] name = "sea-schema" -version = "0.15.0" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad52149fc81836ea7424c3425d8f6ed8ad448dd16d2e4f6a3907ba46f3f2fd78" +checksum = "2239ff574c04858ca77485f112afea1a15e53135d3097d0c86509cef1def1338" dependencies = [ "futures", "sea-query", @@ -3273,7 +3188,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3338,7 +3253,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3466,6 +3381,9 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -3516,21 +3434,11 @@ dependencies = [ "der", ] -[[package]] -name = "sqlformat" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" -dependencies = [ - "nom", - "unicode_categories", -] - [[package]] name = "sqlx" -version = "0.7.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" dependencies = [ "sqlx-core", "sqlx-macros", @@ -3541,67 +3449,62 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ - "ahash", "async-io 1.13.0", "async-std", - "atoi", - "byteorder", + "base64 0.22.1", "bytes", "crc", "crossbeam-queue", "either", - "event-listener 2.5.3", - "futures-channel", + "event-listener 5.4.1", "futures-core", "futures-intrusive", "futures-io", "futures-util", + "hashbrown 0.15.5", "hashlink", - "hex", "indexmap", "log", "memchr", "native-tls", "once_cell", - "paste", "percent-encoding", "serde", "serde_json", "sha2", "smallvec", - "sqlformat", - "thiserror 1.0.69", + "thiserror", "tracing", "url", ] [[package]] name = "sqlx-macros" -version = "0.7.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", - "syn 1.0.109", + "syn", ] [[package]] name = "sqlx-macros-core" -version = "0.7.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ "async-std", "dotenvy", "either", - "heck 0.4.1", + "heck 0.5.0", "hex", "once_cell", "proc-macro2", @@ -3613,19 +3516,18 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 1.0.109", - "tempfile", + "syn", "url", ] [[package]] name = "sqlx-mysql" -version = "0.7.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", - "base64 0.21.7", + "base64 0.22.1", "bitflags 2.11.1", "byteorder", "bytes", @@ -3655,19 +3557,19 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 1.0.69", + "thiserror", "tracing", "whoami", ] [[package]] name = "sqlx-postgres" -version = "0.7.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", - "base64 0.21.7", + "base64 0.22.1", "bitflags 2.11.1", "byteorder", "crc", @@ -3675,7 +3577,6 @@ dependencies = [ "etcetera", "futures-channel", "futures-core", - "futures-io", "futures-util", "hex", "hkdf", @@ -3693,16 +3594,16 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 1.0.69", + "thiserror", "tracing", "whoami", ] [[package]] name = "sqlx-sqlite" -version = "0.7.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" dependencies = [ "atoi", "flume", @@ -3715,10 +3616,11 @@ dependencies = [ "log", "percent-encoding", "serde", + "serde_urlencoded", "sqlx-core", + "thiserror", "tracing", "url", - "urlencoding", ] [[package]] @@ -3777,17 +3679,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.117" @@ -3807,7 +3698,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -3833,33 +3724,13 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "thiserror-impl", ] [[package]] @@ -3870,7 +3741,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -4032,7 +3903,7 @@ checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" dependencies = [ "crossbeam-channel", "symlink", - "thiserror 2.0.18", + "thiserror", "time", "tracing-subscriber", ] @@ -4045,7 +3916,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -4154,7 +4025,7 @@ checksum = "a1249a628de3ad34b821ecb1001355bca3940bcb2f88558f1a8bd82e977f75b5" dependencies = [ "proc-macro-hack", "quote", - "syn 2.0.117", + "syn", "unic-langid-impl", ] @@ -4203,12 +4074,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "unicode_categories" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" - [[package]] name = "universal-hash" version = "0.5.1" @@ -4231,12 +4096,6 @@ dependencies = [ "serde", ] -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -4378,7 +4237,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wasm-bindgen-shared", ] @@ -4487,7 +4346,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -4498,7 +4357,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -4718,7 +4577,7 @@ dependencies = [ "heck 0.5.0", "indexmap", "prettyplease", - "syn 2.0.117", + "syn", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -4734,7 +4593,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -4782,6 +4641,12 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yoke" version = "0.8.2" @@ -4801,7 +4666,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -4822,7 +4687,7 @@ checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -4842,7 +4707,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", "synstructure", ] @@ -4883,7 +4748,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] diff --git a/extensions/pagetop-seaorm/Cargo.toml b/extensions/pagetop-seaorm/Cargo.toml index 404bb84b..66034137 100644 --- a/extensions/pagetop-seaorm/Cargo.toml +++ b/extensions/pagetop-seaorm/Cargo.toml @@ -28,9 +28,9 @@ futures = "0.3" url = "2.5" [dependencies.sea-orm] -version = "~1.0" +version = "1.1" features = ["debug-print", "macros", "runtime-async-std-native-tls"] default-features = false [dependencies.sea-schema] -version = "~0.15" +version = "0.16" diff --git a/extensions/pagetop-seaorm/README.md b/extensions/pagetop-seaorm/README.md index a687828f..0a1de4f5 100644 --- a/extensions/pagetop-seaorm/README.md +++ b/extensions/pagetop-seaorm/README.md @@ -42,7 +42,7 @@ Para MySQL o PostgreSQL añade también `db_user`, `db_pass`, `db_host` y `db_po **Declara la extensión** en tu aplicación o en la extensión que la requiera: -```rust,no_run +```rust,ignore use pagetop::prelude::*; struct MyApp; @@ -70,7 +70,6 @@ async fn main() -> std::io::Result<()> { ```rust,no_run use pagetop_seaorm::db::*; -#[derive(DeriveMigrationName)] pub struct Migration; #[async_trait::async_trait] @@ -111,27 +110,22 @@ Este *crate* se apoya en bibliotecas del ecosistema [SeaQL](https://github.com/S usada por el módulo de migraciones para interrogar la estructura real de la base de datos (tablas, columnas, índices y claves externas). -También incorpora código adaptado de las siguientes fuentes: +El módulo de migraciones (`src/db/migration/`) incorpora una adaptación de +[sea-orm-migration](https://crates.io/crates/sea-orm-migration). El código que se integra procede de +la versión [**1.1.20**](https://github.com/SeaQL/sea-orm/tree/1.1.20/sea-orm-migration) en lugar de +usarlo como dependencia ya que su paradigma de CLI no es compatible con el ciclo de vida de las +extensiones de PageTop, donde las migraciones deben ejecutarse durante la inicialización de cada +extensión. Los ficheros adaptados del original son: -* [**sea-orm-migration (v1.0.0)**](https://github.com/SeaQL/sea-orm/tree/1.0.0/sea-orm-migration): - El módulo de migraciones (`src/db/migration/`) es una versión personalizada de - [sea-orm-migration](https://crates.io/crates/sea-orm-migration). Se integra directamente en lugar - de usarlo como dependencia porque su paradigma de CLI no es compatible con el ciclo de vida de las - extensiones de PageTop, donde las migraciones deben ejecutarse durante la inicialización de cada - extensión. Los ficheros adaptados del original son: - - | Original en `sea-orm-migration` | Observaciones | - |---------------------------------|-----------------------------------------| - | `lib.rs` | Excluye módulos y exportaciones del CLI | - | `connection.rs` | Integración completa | - | `manager.rs` | Integración completa | - | `migrator.rs` | Omite la gestión de errores del CLI | - | `prelude.rs` | Excluye exportaciones del CLI | - | `seaql_migrations.rs` | Integración completa | - -* [**loco-rs/loco**](https://github.com/loco-rs/loco/blob/master/src/schema.rs): El módulo - `src/db/migration/schema.rs`, con funciones de ayuda para definir columnas de tablas de forma - ergonómica, está adaptado del fichero `src/schema.rs` del proyecto [Loco](https://loco.rs/). +| Archivos | Observaciones | +|----------------------------|--------------------------------------------------------------| +| `lib.rs` en `migration.rs` | Excluye módulos y exportaciones del CLI | +| `connection.rs` | Integración completa | +| `manager.rs` | Adapta *features* propias | +| `migrator.rs` | Adapta *features* propias y omite gestión de errores del CLI | +| `prelude.rs` | Excluye exportaciones del CLI | +| `schema.rs` | Integración ajustada con cambios menores | +| `seaql_migrations.rs` | Integración completa | ## 🚧 Advertencia diff --git a/extensions/pagetop-seaorm/src/db.rs b/extensions/pagetop-seaorm/src/db.rs index 70b8e57c..d1188a35 100644 --- a/extensions/pagetop-seaorm/src/db.rs +++ b/extensions/pagetop-seaorm/src/db.rs @@ -11,17 +11,7 @@ use sea_orm::{ConnectionTrait, DatabaseBackend, Statement}; mod dbconn; pub(crate) use dbconn::{run_now, DBCONN}; -// The migration module is a customized version of the sea_orm_migration module (v1.0.0) -// https://github.com/SeaQL/sea-orm/tree/1.0.0/sea-orm-migration to avoid errors caused by the -// package paradigm of PageTop. Files integrated from original: -// -// lib.rs => db/migration.rs . . . . . . . . . . . . . . (excluding some modules and exports) -// connection.rs => db/migration/connection.rs . . . . . . . . . . . . . . (full integration) -// manager.rs => db/migration/manager.rs . . . . . . . . . . . . . . . . . (full integration) -// migrator.rs => db/migration/migrator.rs . . . . . . . . . . . .(omitting error management) -// prelude.rs => db/migration/prelude.rs . . . . . . . . . . . . . . . . . . . (avoiding CLI) -// seaql_migrations.rs => db/migration/seaql_migrations.rs . . . . . . . . (full integration) -// +// Adaptación de `sea-orm-migration` (ver §Créditos en README.md). mod migration; pub use migration::prelude::*; pub use migration::schema::*; diff --git a/extensions/pagetop-seaorm/src/db/migration/connection.rs b/extensions/pagetop-seaorm/src/db/migration/connection.rs index 116185e4..7c937a37 100644 --- a/extensions/pagetop-seaorm/src/db/migration/connection.rs +++ b/extensions/pagetop-seaorm/src/db/migration/connection.rs @@ -11,7 +11,7 @@ pub enum SchemaManagerConnection<'c> { } #[async_trait::async_trait] -impl<'c> ConnectionTrait for SchemaManagerConnection<'c> { +impl ConnectionTrait for SchemaManagerConnection<'_> { fn get_database_backend(&self) -> DbBackend { match self { SchemaManagerConnection::Connection(conn) => conn.get_database_backend(), @@ -56,7 +56,7 @@ impl<'c> ConnectionTrait for SchemaManagerConnection<'c> { } #[async_trait::async_trait] -impl<'c> TransactionTrait for SchemaManagerConnection<'c> { +impl TransactionTrait for SchemaManagerConnection<'_> { async fn begin(&self) -> Result { match self { SchemaManagerConnection::Connection(conn) => conn.begin().await, @@ -86,7 +86,7 @@ impl<'c> TransactionTrait for SchemaManagerConnection<'c> { ) -> Pin> + Send + 'a>> + Send, T: Send, - E: std::error::Error + Send, + E: std::fmt::Display + std::fmt::Debug + Send, { match self { SchemaManagerConnection::Connection(conn) => conn.transaction(callback).await, @@ -106,7 +106,7 @@ impl<'c> TransactionTrait for SchemaManagerConnection<'c> { ) -> Pin> + Send + 'a>> + Send, T: Send, - E: std::error::Error + Send, + E: std::fmt::Display + std::fmt::Debug + Send, { match self { SchemaManagerConnection::Connection(conn) => { diff --git a/extensions/pagetop-seaorm/src/db/migration/manager.rs b/extensions/pagetop-seaorm/src/db/migration/manager.rs index d1cc3b6a..3f962bdf 100644 --- a/extensions/pagetop-seaorm/src/db/migration/manager.rs +++ b/extensions/pagetop-seaorm/src/db/migration/manager.rs @@ -2,11 +2,12 @@ use super::{IntoSchemaManagerConnection, SchemaManagerConnection}; use sea_orm::sea_query::{ extension::postgres::{TypeAlterStatement, TypeCreateStatement, TypeDropStatement}, ForeignKeyCreateStatement, ForeignKeyDropStatement, IndexCreateStatement, IndexDropStatement, - TableAlterStatement, TableCreateStatement, TableDropStatement, TableRenameStatement, - TableTruncateStatement, + SelectStatement, TableAlterStatement, TableCreateStatement, TableDropStatement, + TableRenameStatement, TableTruncateStatement, }; use sea_orm::{ConnectionTrait, DbBackend, DbErr, StatementBuilder}; -use sea_schema::{mysql::MySql, postgres::Postgres, probe::SchemaProbe, sqlite::Sqlite}; +#[allow(unused_imports)] +use sea_schema::probe::SchemaProbe; /// Helper struct for writing migration scripts in migration file pub struct SchemaManager<'c> { @@ -41,7 +42,7 @@ impl<'c> SchemaManager<'c> { } /// Schema Creation -impl<'c> SchemaManager<'c> { +impl SchemaManager<'_> { pub async fn create_table(&self, stmt: TableCreateStatement) -> Result<(), DbErr> { self.exec_stmt(stmt).await } @@ -60,7 +61,7 @@ impl<'c> SchemaManager<'c> { } /// Schema Mutation -impl<'c> SchemaManager<'c> { +impl SchemaManager<'_> { pub async fn alter_table(&self, stmt: TableAlterStatement) -> Result<(), DbErr> { self.exec_stmt(stmt).await } @@ -95,7 +96,7 @@ impl<'c> SchemaManager<'c> { } /// Schema Inspection. -impl<'c> SchemaManager<'c> { +impl SchemaManager<'_> { pub async fn has_table(&self, table: T) -> Result where T: AsRef, @@ -103,42 +104,54 @@ impl<'c> SchemaManager<'c> { has_table(&self.conn, table).await } - pub async fn has_column(&self, table: T, column: C) -> Result + pub async fn has_column(&self, _table: T, _column: C) -> Result where T: AsRef, C: AsRef, { - let stmt = match self.conn.get_database_backend() { - DbBackend::MySql => MySql.has_column(table, column), - DbBackend::Postgres => Postgres.has_column(table, column), - DbBackend::Sqlite => Sqlite.has_column(table, column), + let _stmt: SelectStatement = match self.conn.get_database_backend() { + #[cfg(feature = "mysql")] + DbBackend::MySql => sea_schema::mysql::MySql.has_column(_table, _column), + #[cfg(feature = "postgres")] + DbBackend::Postgres => sea_schema::postgres::Postgres.has_column(_table, _column), + #[cfg(feature = "sqlite")] + DbBackend::Sqlite => sea_schema::sqlite::Sqlite.has_column(_table, _column), + #[allow(unreachable_patterns)] + other => panic!("{other:?} feature is off"), }; + #[allow(unreachable_code)] let builder = self.conn.get_database_backend(); let res = self .conn - .query_one(builder.build(&stmt)) + .query_one(builder.build(&_stmt)) .await? .ok_or_else(|| DbErr::Custom("Failed to check column exists".to_owned()))?; res.try_get("", "has_column") } - pub async fn has_index(&self, table: T, index: I) -> Result + pub async fn has_index(&self, _table: T, _index: I) -> Result where T: AsRef, I: AsRef, { - let stmt = match self.conn.get_database_backend() { - DbBackend::MySql => MySql.has_index(table, index), - DbBackend::Postgres => Postgres.has_index(table, index), - DbBackend::Sqlite => Sqlite.has_index(table, index), + let _stmt: SelectStatement = match self.conn.get_database_backend() { + #[cfg(feature = "mysql")] + DbBackend::MySql => sea_schema::mysql::MySql.has_index(_table, _index), + #[cfg(feature = "postgres")] + DbBackend::Postgres => sea_schema::postgres::Postgres.has_index(_table, _index), + #[cfg(feature = "sqlite")] + DbBackend::Sqlite => sea_schema::sqlite::Sqlite.has_index(_table, _index), + #[allow(unreachable_patterns)] + other => panic!("{other:?} feature is off"), }; + #[allow(unreachable_code)] let builder = self.conn.get_database_backend(); let res = self .conn - .query_one(builder.build(&stmt)) + .query_one(builder.build(&_stmt)) .await? .ok_or_else(|| DbErr::Custom("Failed to check index exists".to_owned()))?; @@ -146,20 +159,26 @@ impl<'c> SchemaManager<'c> { } } -pub(crate) async fn has_table(conn: &C, table: T) -> Result +pub(crate) async fn has_table(conn: &C, _table: T) -> Result where C: ConnectionTrait, T: AsRef, { - let stmt = match conn.get_database_backend() { - DbBackend::MySql => MySql.has_table(table), - DbBackend::Postgres => Postgres.has_table(table), - DbBackend::Sqlite => Sqlite.has_table(table), + let _stmt: SelectStatement = match conn.get_database_backend() { + #[cfg(feature = "mysql")] + DbBackend::MySql => sea_schema::mysql::MySql.has_table(_table), + #[cfg(feature = "postgres")] + DbBackend::Postgres => sea_schema::postgres::Postgres.has_table(_table), + #[cfg(feature = "sqlite")] + DbBackend::Sqlite => sea_schema::sqlite::Sqlite.has_table(_table), + #[allow(unreachable_patterns)] + other => panic!("{other:?} feature is off"), }; + #[allow(unreachable_code)] let builder = conn.get_database_backend(); let res = conn - .query_one(builder.build(&stmt)) + .query_one(builder.build(&_stmt)) .await? .ok_or_else(|| DbErr::Custom("Failed to check table exists".to_owned()))?; diff --git a/extensions/pagetop-seaorm/src/db/migration/migrator.rs b/extensions/pagetop-seaorm/src/db/migration/migrator.rs index 06611412..45ecdbac 100644 --- a/extensions/pagetop-seaorm/src/db/migration/migrator.rs +++ b/extensions/pagetop-seaorm/src/db/migration/migrator.rs @@ -7,7 +7,7 @@ use std::time::SystemTime; use pagetop::trace::info; use sea_orm::sea_query::{ - self, extension::postgres::Type, Alias, Expr, ForeignKey, IntoIden, JoinType, Order, Query, + self, extension::postgres::Type, Alias, Expr, ExprTrait, ForeignKey, IntoIden, Order, Query, SelectStatement, SimpleExpr, Table, }; use sea_orm::{ @@ -15,7 +15,8 @@ use sea_orm::{ DynIden, EntityTrait, FromQueryResult, Iterable, QueryFilter, Schema, Statement, TransactionTrait, }; -use sea_schema::{mysql::MySql, postgres::Postgres, probe::SchemaProbe, sqlite::Sqlite}; +#[allow(unused_imports)] +use sea_schema::probe::SchemaProbe; use super::{seaql_migrations, IntoSchemaManagerConnection, MigrationTrait, SchemaManager}; @@ -445,9 +446,14 @@ where C: ConnectionTrait, { match db.get_database_backend() { - DbBackend::MySql => MySql.query_tables(), - DbBackend::Postgres => Postgres.query_tables(), - DbBackend::Sqlite => Sqlite.query_tables(), + #[cfg(feature = "mysql")] + DbBackend::MySql => sea_schema::mysql::MySql.query_tables(), + #[cfg(feature = "postgres")] + DbBackend::Postgres => sea_schema::postgres::Postgres.query_tables(), + #[cfg(feature = "sqlite")] + DbBackend::Sqlite => sea_schema::sqlite::Sqlite.query_tables(), + #[allow(unreachable_patterns)] + other => panic!("{other:?} feature is off"), } } @@ -456,9 +462,14 @@ where C: ConnectionTrait, { match db.get_database_backend() { - DbBackend::MySql => MySql::get_current_schema(), - DbBackend::Postgres => Postgres::get_current_schema(), - DbBackend::Sqlite => unimplemented!(), + #[cfg(feature = "mysql")] + DbBackend::MySql => sea_schema::mysql::MySql::get_current_schema(), + #[cfg(feature = "postgres")] + DbBackend::Postgres => sea_schema::postgres::Postgres::get_current_schema(), + #[cfg(feature = "sqlite")] + DbBackend::Sqlite => sea_schema::sqlite::Sqlite::get_current_schema(), + #[allow(unreachable_patterns)] + other => panic!("{other:?} feature is off"), } } @@ -490,7 +501,7 @@ where )) .cond_where( Condition::all() - .add(Expr::expr(get_current_schema(db)).equals(( + .add(get_current_schema(db).equals(( InformationSchema::TableConstraints, InformationSchema::TableSchema, ))) @@ -508,11 +519,20 @@ where #[derive(DeriveIden)] enum PgType { Table, + Oid, Typname, Typnamespace, Typelem, } +#[derive(DeriveIden)] +enum PgDepend { + Table, + Objid, + Deptype, + Refclassid, +} + #[derive(DeriveIden)] enum PgNamespace { Table, @@ -524,24 +544,28 @@ fn query_pg_types(db: &C) -> SelectStatement where C: ConnectionTrait, { - let mut stmt = Query::select(); - stmt.column(PgType::Typname) + Query::select() + .column(PgType::Typname) .from(PgType::Table) - .join( - JoinType::LeftJoin, + .left_join( PgNamespace::Table, Expr::col((PgNamespace::Table, PgNamespace::Oid)) .equals((PgType::Table, PgType::Typnamespace)), ) - .cond_where( - Condition::all() - .add( - Expr::expr(get_current_schema(db)) - .equals((PgNamespace::Table, PgNamespace::Nspname)), + .left_join( + PgDepend::Table, + Expr::col((PgDepend::Table, PgDepend::Objid)) + .equals((PgType::Table, PgType::Oid)) + .and( + Expr::col((PgDepend::Table, PgDepend::Refclassid)) + .eq(Expr::cust("'pg_extension'::regclass::oid")), ) - .add(Expr::col((PgType::Table, PgType::Typelem)).eq(0)), - ); - stmt + .and(Expr::col((PgDepend::Table, PgDepend::Deptype)).eq(Expr::cust("'e'"))), + ) + .and_where(get_current_schema(db).equals((PgNamespace::Table, PgNamespace::Nspname))) + .and_where(Expr::col((PgType::Table, PgType::Typelem)).eq(0)) + .and_where(Expr::col((PgDepend::Table, PgDepend::Objid)).is_null()) + .take() } trait QueryTable { diff --git a/extensions/pagetop-seaorm/src/db/migration/prelude.rs b/extensions/pagetop-seaorm/src/db/migration/prelude.rs index 5556a094..e2389faa 100644 --- a/extensions/pagetop-seaorm/src/db/migration/prelude.rs +++ b/extensions/pagetop-seaorm/src/db/migration/prelude.rs @@ -10,4 +10,3 @@ pub use sea_orm; pub use sea_orm::sea_query; pub use sea_orm::sea_query::*; pub use sea_orm::DeriveIden; -pub use sea_orm::DeriveMigrationName; diff --git a/extensions/pagetop-seaorm/src/db/migration/schema.rs b/extensions/pagetop-seaorm/src/db/migration/schema.rs index b6f89d3a..90910277 100644 --- a/extensions/pagetop-seaorm/src/db/migration/schema.rs +++ b/extensions/pagetop-seaorm/src/db/migration/schema.rs @@ -73,6 +73,11 @@ pub fn pk_auto(name: T) -> ColumnDef { integer(name).auto_increment().primary_key().take() } +/// Create a UUID primary key +pub fn pk_uuid(name: T) -> ColumnDef { + uuid(name).primary_key().take() +} + pub fn char_len(col: T, length: u32) -> ColumnDef { ColumnDef::new(col).char_len(length).not_null().take() } @@ -538,11 +543,11 @@ pub fn uuid_uniq(col: T) -> ColumnDef { uuid(col).unique_key().take() } -pub fn custom(col: T, name: T) -> ColumnDef { +pub fn custom(col: T, name: N) -> ColumnDef { ColumnDef::new(col).custom(name).not_null().take() } -pub fn custom_null(col: T, name: T) -> ColumnDef { +pub fn custom_null(col: T, name: N) -> ColumnDef { ColumnDef::new(col).custom(name).null().take() } From 796ae5ce811918c8648a754ad2741b3788bf3208 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Mon, 11 May 2026 15:10:49 +0200 Subject: [PATCH 11/12] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(seaorm):=20Revisa?= =?UTF-8?q?=20y=20mejora=20la=20API=20p=C3=BAblica?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extensions/pagetop-seaorm/README.md | 21 +++--- extensions/pagetop-seaorm/src/config.rs | 6 +- extensions/pagetop-seaorm/src/db.rs | 72 +++++++++++++++++-- extensions/pagetop-seaorm/src/db/dbconn.rs | 17 ++--- extensions/pagetop-seaorm/src/db/migration.rs | 4 +- extensions/pagetop-seaorm/src/lib.rs | 3 +- 6 files changed, 92 insertions(+), 31 deletions(-) diff --git a/extensions/pagetop-seaorm/README.md b/extensions/pagetop-seaorm/README.md index 0a1de4f5..fff02d6d 100644 --- a/extensions/pagetop-seaorm/README.md +++ b/extensions/pagetop-seaorm/README.md @@ -38,7 +38,8 @@ db_name = "my_app.db" max_pool_size = 5 ``` -Para MySQL o PostgreSQL añade también `db_user`, `db_pass`, `db_host` y `db_port`. +Para MySQL o PostgreSQL añade también `db_user`, `db_pass` y `db_host`. El campo `db_port` es +opcional; si se omite se usa el puerto predeterminado del motor. **Declara la extensión** en tu aplicación o en la extensión que la requiera: @@ -117,15 +118,15 @@ usarlo como dependencia ya que su paradigma de CLI no es compatible con el ciclo extensiones de PageTop, donde las migraciones deben ejecutarse durante la inicialización de cada extensión. Los ficheros adaptados del original son: -| Archivos | Observaciones | -|----------------------------|--------------------------------------------------------------| -| `lib.rs` en `migration.rs` | Excluye módulos y exportaciones del CLI | -| `connection.rs` | Integración completa | -| `manager.rs` | Adapta *features* propias | -| `migrator.rs` | Adapta *features* propias y omite gestión de errores del CLI | -| `prelude.rs` | Excluye exportaciones del CLI | -| `schema.rs` | Integración ajustada con cambios menores | -| `seaql_migrations.rs` | Integración completa | +| Archivos | Observaciones | +|-----------------------|--------------------------------------------------------------------------| +| `lib.rs` | Incluido en `migration.rs`, descarta módulos y exportaciones del CLI | +| `connection.rs` | Integración completa | +| `manager.rs` | Adapta *features* propias | +| `migrator.rs` | Adapta *features* propias y omite gestión de errores del CLI | +| `prelude.rs` | Excluye exportaciones del CLI | +| `schema.rs` | Integra con ajustes, original de [loco](https://github.com/loco-rs/loco) | +| `seaql_migrations.rs` | Integración completa | ## 🚧 Advertencia diff --git a/extensions/pagetop-seaorm/src/config.rs b/extensions/pagetop-seaorm/src/config.rs index bec565b3..b1276c50 100644 --- a/extensions/pagetop-seaorm/src/config.rs +++ b/extensions/pagetop-seaorm/src/config.rs @@ -34,7 +34,6 @@ include_config!(SETTINGS: Settings => [ "database.db_user" => "", "database.db_pass" => "", "database.db_host" => "localhost", - "database.db_port" => 0, "database.max_pool_size" => 5, ]); @@ -57,8 +56,9 @@ pub struct Database { pub db_pass: String, /// Servidor de conexión a la base de datos (para mysql/postgres). pub db_host: String, - /// Puerto de conexión a la base de datos, normalmente 3306 (para mysql) ó 5432 (para postgres). - pub db_port: u16, + /// Puerto de conexión a la base de datos (para mysql/postgres). Si es `None` se usa el puerto + /// predeterminado para el motor: 3306 para MySQL y 5432 para PostgreSQL. + pub db_port: Option, /// Número máximo de conexiones habilitadas. pub max_pool_size: u32, } diff --git a/extensions/pagetop-seaorm/src/db.rs b/extensions/pagetop-seaorm/src/db.rs index d1188a35..f954d9ce 100644 --- a/extensions/pagetop-seaorm/src/db.rs +++ b/extensions/pagetop-seaorm/src/db.rs @@ -1,7 +1,7 @@ use pagetop::core::TypeInfo; use pagetop::trace; -pub use url::Url as DbUri; +pub(crate) use url::Url as DbUri; pub use sea_orm::error::{DbErr, RuntimeErr}; pub use sea_orm::{DatabaseConnection as DbConn, ExecResult, QueryResult}; @@ -16,7 +16,30 @@ mod migration; pub use migration::prelude::*; pub use migration::schema::*; -pub async fn query(stmt: &mut Q) -> Result, DbErr> { +/// Ejecuta una consulta para devolver todas las filas resultantes. +/// +/// Acepta cualquier tipo que implemente [`QueryStatementWriter`] (p. ej. [`SelectStatement`]) y +/// serializa la sentencia al dialecto de la base de datos configurada antes de ejecutarla. Cada +/// fila se devuelve como un [`QueryResult`] sin tipar; extrae los valores con +/// [`QueryResult::try_get`]. +/// +/// ```rust,no_run +/// use pagetop_seaorm::db::*; +/// +/// async fn example() -> Result<(), DbErr> { +/// let mut stmt = Query::select() +/// .column(Asterisk) +/// .from(Alias::new("users")) +/// .to_owned(); +/// let rows = fetch_all(&mut stmt).await?; +/// for row in rows { +/// let name: String = row.try_get("", "name")?; +/// println!("{name}"); +/// } +/// Ok(()) +/// } +/// ``` +pub async fn fetch_all(stmt: &mut Q) -> Result, DbErr> { let dbconn = &*DBCONN; let dbbackend = dbconn.get_database_backend(); dbconn @@ -31,7 +54,30 @@ pub async fn query(stmt: &mut Q) -> Result(stmt: &mut Q) -> Result, DbErr> { +/// Ejecuta una consulta y devuelve sólo la primera fila, si existe. +/// +/// Funciona igual que [`fetch_all`] pero detiene la ejecución tras la primera fila y devuelve +/// `None` si la consulta no produce resultados. +/// +/// ```rust,no_run +/// use pagetop_seaorm::db::*; +/// +/// async fn example() -> Result<(), DbErr> { +/// let mut stmt = Query::select() +/// .column(Asterisk) +/// .from(Alias::new("users")) +/// .and_where(Expr::col(Alias::new("id")).eq(1)) +/// .to_owned(); +/// if let Some(row) = fetch_one(&mut stmt).await? { +/// let name: String = row.try_get("", "name")?; +/// println!("{name}"); +/// } +/// Ok(()) +/// } +/// ``` +pub async fn fetch_one( + stmt: &mut Q, +) -> Result, DbErr> { let dbconn = &*DBCONN; let dbbackend = dbconn.get_database_backend(); dbconn @@ -46,11 +92,27 @@ pub async fn exec(stmt: &mut Q) -> Result Result { +/// Ejecuta una sentencia SQL en crudo (INSERT, UPDATE, DELETE…) y devuelve el resultado de +/// la operación. +/// +/// A diferencia de [`fetch_all`] y [`fetch_one`], no construye la consulta, sino que la recibe como +/// cadena ya formada. Útil para sentencias avanzadas o para migraciones puntuales. El +/// [`ExecResult`] devuelto permite consultar las filas afectadas o el último ID insertado. +/// +/// ```rust,no_run +/// use pagetop_seaorm::db::*; +/// +/// async fn example() -> Result<(), DbErr> { +/// let result = execute("DELETE FROM sessions WHERE expired = 1").await?; +/// println!("Filas eliminadas: {}", result.rows_affected()); +/// Ok(()) +/// } +/// ``` +pub async fn execute(stmt: impl Into) -> Result { let dbconn = &*DBCONN; let dbbackend = dbconn.get_database_backend(); dbconn - .execute(Statement::from_string(dbbackend, stmt)) + .execute(Statement::from_string(dbbackend, stmt.into())) .await } diff --git a/extensions/pagetop-seaorm/src/db/dbconn.rs b/extensions/pagetop-seaorm/src/db/dbconn.rs index bd227956..e4881c08 100644 --- a/extensions/pagetop-seaorm/src/db/dbconn.rs +++ b/extensions/pagetop-seaorm/src/db/dbconn.rs @@ -35,10 +35,8 @@ pub static DBCONN: LazyLock = LazyLock::new(|| { tmp_uri .set_password(Some(config::SETTINGS.database.db_pass.as_str())) .unwrap(); - if config::SETTINGS.database.db_port != 0 { - tmp_uri - .set_port(Some(config::SETTINGS.database.db_port)) - .unwrap(); + if let Some(port) = config::SETTINGS.database.db_port { + tmp_uri.set_port(Some(port)).unwrap(); } tmp_uri } @@ -51,13 +49,10 @@ pub static DBCONN: LazyLock = LazyLock::new(|| { .as_str(), ) .unwrap(), - _ => { - trace::error!( - "Unrecognized database type \"{}\"", - &config::SETTINGS.database.db_type - ); - DbUri::parse("").unwrap() - } + _ => panic!( + "Unrecognized database type \"{}\"", + config::SETTINGS.database.db_type + ), }; run_now(Database::connect::({ diff --git a/extensions/pagetop-seaorm/src/db/migration.rs b/extensions/pagetop-seaorm/src/db/migration.rs index 29314bf6..0c32c10a 100644 --- a/extensions/pagetop-seaorm/src/db/migration.rs +++ b/extensions/pagetop-seaorm/src/db/migration.rs @@ -28,6 +28,8 @@ pub trait MigrationTrait: MigrationName + Send + Sync { /// Define actions to perform when rolling back the migration async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { - Err(DbErr::Migration("We Don't Do That Here".to_owned())) + Err(DbErr::Migration( + "Rollback not implemented for this migration".to_owned(), + )) } } diff --git a/extensions/pagetop-seaorm/src/lib.rs b/extensions/pagetop-seaorm/src/lib.rs index 103d95eb..0b686cb2 100644 --- a/extensions/pagetop-seaorm/src/lib.rs +++ b/extensions/pagetop-seaorm/src/lib.rs @@ -39,7 +39,8 @@ db_name = "my_app.db" max_pool_size = 5 ``` -Para MySQL o PostgreSQL añade también `db_user`, `db_pass`, `db_host` y `db_port`. +Para MySQL o PostgreSQL añade también `db_user`, `db_pass` y `db_host`. El campo `db_port` es +opcional; si se omite se usa el puerto predeterminado del motor. **Declara la extensión** en tu aplicación o en la extensión que la requiera: From 026448e51108ef76d077c3d81ea7dbf5e7c2fc06 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Fri, 15 May 2026 00:22:55 +0200 Subject: [PATCH 12/12] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20(seaorm):=20Separa?= =?UTF-8?q?=20m=C3=B3dulo=20`migration`=20de=20`db`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `db::*` queda como API de consultas (connection, fetch_*). - `migration::*` sube a primer nivel con su propia documentación. - `DBCONN` y `run_now` se trasladan a la raíz de la extensión. - Actualiza README.md y docs para reflejar la nueva estructura. --- extensions/pagetop-seaorm/README.md | 6 +- extensions/pagetop-seaorm/src/db.rs | 130 +++++---------- extensions/pagetop-seaorm/src/db/dbconn.rs | 64 ------- extensions/pagetop-seaorm/src/db/migration.rs | 35 ---- .../src/db/migration/prelude.rs | 12 -- extensions/pagetop-seaorm/src/lib.rs | 67 +++++++- extensions/pagetop-seaorm/src/migration.rs | 156 ++++++++++++++++++ .../src/{db => }/migration/connection.rs | 0 .../src/{db => }/migration/manager.rs | 0 .../src/{db => }/migration/migrator.rs | 0 .../src/{db => }/migration/schema.rs | 26 ++- .../{db => }/migration/seaql_migrations.rs | 0 12 files changed, 279 insertions(+), 217 deletions(-) delete mode 100644 extensions/pagetop-seaorm/src/db/dbconn.rs delete mode 100644 extensions/pagetop-seaorm/src/db/migration.rs delete mode 100644 extensions/pagetop-seaorm/src/db/migration/prelude.rs create mode 100644 extensions/pagetop-seaorm/src/migration.rs rename extensions/pagetop-seaorm/src/{db => }/migration/connection.rs (100%) rename extensions/pagetop-seaorm/src/{db => }/migration/manager.rs (100%) rename extensions/pagetop-seaorm/src/{db => }/migration/migrator.rs (100%) rename extensions/pagetop-seaorm/src/{db => }/migration/schema.rs (96%) rename extensions/pagetop-seaorm/src/{db => }/migration/seaql_migrations.rs (100%) diff --git a/extensions/pagetop-seaorm/README.md b/extensions/pagetop-seaorm/README.md index fff02d6d..42738cd0 100644 --- a/extensions/pagetop-seaorm/README.md +++ b/extensions/pagetop-seaorm/README.md @@ -69,7 +69,7 @@ async fn main() -> std::io::Result<()> { **Escribe las migraciones** usando la API de SeaORM: ```rust,no_run -use pagetop_seaorm::db::*; +use pagetop_seaorm::migration::*; pub struct Migration; @@ -111,7 +111,7 @@ Este *crate* se apoya en bibliotecas del ecosistema [SeaQL](https://github.com/S usada por el módulo de migraciones para interrogar la estructura real de la base de datos (tablas, columnas, índices y claves externas). -El módulo de migraciones (`src/db/migration/`) incorpora una adaptación de +El módulo de migraciones (`src/migration/`) incorpora una adaptación de [sea-orm-migration](https://crates.io/crates/sea-orm-migration). El código que se integra procede de la versión [**1.1.20**](https://github.com/SeaQL/sea-orm/tree/1.1.20/sea-orm-migration) en lugar de usarlo como dependencia ya que su paradigma de CLI no es compatible con el ciclo de vida de las @@ -124,7 +124,7 @@ extensión. Los ficheros adaptados del original son: | `connection.rs` | Integración completa | | `manager.rs` | Adapta *features* propias | | `migrator.rs` | Adapta *features* propias y omite gestión de errores del CLI | -| `prelude.rs` | Excluye exportaciones del CLI | +| `prelude.rs` | Absorbido en `migration.rs`, descarta exportaciones del CLI | | `schema.rs` | Integra con ajustes, original de [loco](https://github.com/loco-rs/loco) | | `seaql_migrations.rs` | Integración completa | diff --git a/extensions/pagetop-seaorm/src/db.rs b/extensions/pagetop-seaorm/src/db.rs index f954d9ce..2b47c399 100644 --- a/extensions/pagetop-seaorm/src/db.rs +++ b/extensions/pagetop-seaorm/src/db.rs @@ -1,30 +1,52 @@ -use pagetop::core::TypeInfo; -use pagetop::trace; +//! API completa de SeaORM para operaciones con la base de datos. +//! +//! Re-exporta el *prelude* de SeaORM (entidades, traits, tipos de valor, macros de derivación…) +//! y expone tres funciones de consulta propias. Con una sola importación tienes todo lo necesario +//! para definir entidades y realizar operaciones CRUD: +//! +//! ```rust,ignore +//! use pagetop_seaorm::db::*; +//! ``` +//! +//! Para definir el esquema de la base de datos o escribir migraciones usa además +//! [`crate::migration`]. -pub(crate) use url::Url as DbUri; +pub use sea_orm::prelude::*; -pub use sea_orm::error::{DbErr, RuntimeErr}; -pub use sea_orm::{DatabaseConnection as DbConn, ExecResult, QueryResult}; +use sea_orm::sea_query::{ + MysqlQueryBuilder, PostgresQueryBuilder, QueryStatementWriter, SqliteQueryBuilder, +}; +use sea_orm::{DatabaseBackend, ExecResult, Statement}; -use sea_orm::{ConnectionTrait, DatabaseBackend, Statement}; - -mod dbconn; -pub(crate) use dbconn::{run_now, DBCONN}; - -// Adaptación de `sea-orm-migration` (ver §Créditos en README.md). -mod migration; -pub use migration::prelude::*; -pub use migration::schema::*; +/// Devuelve una referencia al pool de conexiones para usarla con el sistema de entidades. +/// +/// Permite pasar la conexión a los métodos `all`, `one`, `exec`, etc. del sistema de entidades +/// de SeaORM. El coste de esta llamada es prácticamente nulo: sólo devuelve una referencia a un +/// valor inicializado una sola vez al arrancar la aplicación. +/// +/// ```rust,no_run +/// use pagetop_seaorm::db::*; +/// +/// // Consultas tipadas con el sistema de entidades de SeaORM: +/// // let users = User::find().all(connection()).await?; +/// // let user = User::find_by_id(1).one(connection()).await?; +/// // User::insert(model).exec(connection()).await?; +/// let _conn = connection(); +/// ``` +pub fn connection() -> &'static DatabaseConnection { + &super::DBCONN +} /// Ejecuta una consulta para devolver todas las filas resultantes. /// -/// Acepta cualquier tipo que implemente [`QueryStatementWriter`] (p. ej. [`SelectStatement`]) y +/// Acepta cualquier tipo que implemente [`crate::migration::QueryStatementWriter`] (p. ej. [`crate::migration::SelectStatement`]) y /// serializa la sentencia al dialecto de la base de datos configurada antes de ejecutarla. Cada /// fila se devuelve como un [`QueryResult`] sin tipar; extrae los valores con /// [`QueryResult::try_get`]. /// /// ```rust,no_run /// use pagetop_seaorm::db::*; +/// use pagetop_seaorm::migration::*; /// /// async fn example() -> Result<(), DbErr> { /// let mut stmt = Query::select() @@ -40,7 +62,7 @@ pub use migration::schema::*; /// } /// ``` pub async fn fetch_all(stmt: &mut Q) -> Result, DbErr> { - let dbconn = &*DBCONN; + let dbconn = &*super::DBCONN; let dbbackend = dbconn.get_database_backend(); dbconn .query_all(Statement::from_string( @@ -61,6 +83,7 @@ pub async fn fetch_all(stmt: &mut Q) -> Result Result<(), DbErr> { /// let mut stmt = Query::select() @@ -78,7 +101,7 @@ pub async fn fetch_all(stmt: &mut Q) -> Result( stmt: &mut Q, ) -> Result, DbErr> { - let dbconn = &*DBCONN; + let dbconn = &*super::DBCONN; let dbbackend = dbconn.get_database_backend(); dbconn .query_one(Statement::from_string( @@ -95,8 +118,8 @@ pub async fn fetch_one( /// Ejecuta una sentencia SQL en crudo (INSERT, UPDATE, DELETE…) y devuelve el resultado de /// la operación. /// -/// A diferencia de [`fetch_all`] y [`fetch_one`], no construye la consulta, sino que la recibe como -/// cadena ya formada. Útil para sentencias avanzadas o para migraciones puntuales. El +/// A diferencia de [`fetch_all`] y [`fetch_one`], no construye la consulta, sino que la recibe +/// como cadena ya formada. Útil para sentencias avanzadas o para migraciones puntuales. El /// [`ExecResult`] devuelto permite consultar las filas afectadas o el último ID insertado. /// /// ```rust,no_run @@ -109,76 +132,9 @@ pub async fn fetch_one( /// } /// ``` pub async fn execute(stmt: impl Into) -> Result { - let dbconn = &*DBCONN; + let dbconn = &*super::DBCONN; let dbbackend = dbconn.get_database_backend(); dbconn .execute(Statement::from_string(dbbackend, stmt.into())) .await } - -pub trait MigratorBase { - fn run_up(); - - fn run_down(); -} - -#[rustfmt::skip] -impl MigratorBase for M { - fn run_up() { - if let Err(e) = run_now(Self::up(SchemaManagerConnection::Connection(&DBCONN), None)) { - trace::error!("Migration upgrade failed ({})", e); - }; - } - - fn run_down() { - if let Err(e) = run_now(Self::down(SchemaManagerConnection::Connection(&DBCONN), None)) { - trace::error!("Migration downgrade failed ({})", e); - }; - } -} - -impl MigrationName for M { - fn name(&self) -> &str { - TypeInfo::NameTo(-2).of::() - } -} - -pub type MigrationItem = Box; - -#[macro_export] -macro_rules! install_migrations { - ( $($migration_module:ident),+ $(,)? ) => {{ - use $crate::db::{MigrationItem, MigratorBase, MigratorTrait}; - - struct Migrator; - impl MigratorTrait for Migrator { - fn migrations() -> Vec { - let mut m = Vec::::new(); - $( - m.push(Box::new(migration::$migration_module::Migration)); - )* - m - } - } - Migrator::run_up(); - }}; -} - -#[macro_export] -macro_rules! uninstall_migrations { - ( $($migration_module:ident),+ $(,)? ) => {{ - use $crate::db::{MigrationItem, MigratorBase, MigratorTrait}; - - struct Migrator; - impl MigratorTrait for Migrator { - fn migrations() -> Vec { - let mut m = Vec::::new(); - $( - m.push(Box::new(migration::$migration_module::Migration)); - )* - m - } - } - Migrator::run_down(); - }}; -} diff --git a/extensions/pagetop-seaorm/src/db/dbconn.rs b/extensions/pagetop-seaorm/src/db/dbconn.rs deleted file mode 100644 index e4881c08..00000000 --- a/extensions/pagetop-seaorm/src/db/dbconn.rs +++ /dev/null @@ -1,64 +0,0 @@ -use pagetop::trace; - -use crate::config; -use crate::db::{DbConn, DbUri}; - -use std::sync::LazyLock; - -use sea_orm::{ConnectOptions, Database}; - -pub use futures::executor::block_on as run_now; - -pub static DBCONN: LazyLock = LazyLock::new(|| { - trace::info!( - "Connecting to database \"{}\" using a pool of {} connections", - &config::SETTINGS.database.db_name, - &config::SETTINGS.database.max_pool_size - ); - - let db_uri = match config::SETTINGS.database.db_type.as_str() { - "mysql" | "postgres" => { - let mut tmp_uri = DbUri::parse( - format!( - "{}://{}/{}", - &config::SETTINGS.database.db_type, - &config::SETTINGS.database.db_host, - &config::SETTINGS.database.db_name - ) - .as_str(), - ) - .unwrap(); - tmp_uri - .set_username(config::SETTINGS.database.db_user.as_str()) - .unwrap(); - // https://github.com/launchbadge/sqlx/issues/1624 - tmp_uri - .set_password(Some(config::SETTINGS.database.db_pass.as_str())) - .unwrap(); - if let Some(port) = config::SETTINGS.database.db_port { - tmp_uri.set_port(Some(port)).unwrap(); - } - tmp_uri - } - "sqlite" => DbUri::parse( - format!( - "{}://{}", - &config::SETTINGS.database.db_type, - &config::SETTINGS.database.db_name - ) - .as_str(), - ) - .unwrap(), - _ => panic!( - "Unrecognized database type \"{}\"", - config::SETTINGS.database.db_type - ), - }; - - run_now(Database::connect::({ - let mut db_opt = ConnectOptions::new(db_uri.to_string()); - db_opt.max_connections(config::SETTINGS.database.max_pool_size); - db_opt - })) - .unwrap_or_else(|_| panic!("Failed to connect to database")) -}); diff --git a/extensions/pagetop-seaorm/src/db/migration.rs b/extensions/pagetop-seaorm/src/db/migration.rs deleted file mode 100644 index 0c32c10a..00000000 --- a/extensions/pagetop-seaorm/src/db/migration.rs +++ /dev/null @@ -1,35 +0,0 @@ -//pub mod cli; -pub mod connection; -pub mod manager; -pub mod migrator; -pub mod prelude; -pub mod schema; -pub mod seaql_migrations; -//pub mod util; - -pub use connection::*; -pub use manager::*; -//pub use migrator::*; - -pub use async_trait; -//pub use sea_orm; -//pub use sea_orm::sea_query; -use sea_orm::DbErr; - -pub trait MigrationName { - fn name(&self) -> &str; -} - -/// The migration definition -#[async_trait::async_trait] -pub trait MigrationTrait: MigrationName + Send + Sync { - /// Define actions to perform when applying the migration - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr>; - - /// Define actions to perform when rolling back the migration - async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { - Err(DbErr::Migration( - "Rollback not implemented for this migration".to_owned(), - )) - } -} diff --git a/extensions/pagetop-seaorm/src/db/migration/prelude.rs b/extensions/pagetop-seaorm/src/db/migration/prelude.rs deleted file mode 100644 index e2389faa..00000000 --- a/extensions/pagetop-seaorm/src/db/migration/prelude.rs +++ /dev/null @@ -1,12 +0,0 @@ -//pub use super::cli; - -pub use super::connection::IntoSchemaManagerConnection; -pub use super::connection::SchemaManagerConnection; -pub use super::manager::SchemaManager; -pub use super::migrator::MigratorTrait; -pub use super::{MigrationName, MigrationTrait}; -pub use async_trait; -pub use sea_orm; -pub use sea_orm::sea_query; -pub use sea_orm::sea_query::*; -pub use sea_orm::DeriveIden; diff --git a/extensions/pagetop-seaorm/src/lib.rs b/extensions/pagetop-seaorm/src/lib.rs index 0b686cb2..8af917ab 100644 --- a/extensions/pagetop-seaorm/src/lib.rs +++ b/extensions/pagetop-seaorm/src/lib.rs @@ -70,7 +70,7 @@ async fn main() -> std::io::Result<()> { **Escribe las migraciones** usando la API de SeaORM: ```rust,no_run -use pagetop_seaorm::db::*; +use pagetop_seaorm::migration::*; pub struct Migration; @@ -103,12 +103,75 @@ enum Users { use pagetop::prelude::*; +use sea_orm::{ConnectOptions, Database, DatabaseConnection}; +use url::Url; + +use std::sync::LazyLock; + include_locales!(LOCALES_SEAORM); pub mod config; pub mod db; +pub mod migration; + +pub(crate) use futures::executor::block_on as run_now; + +pub(crate) static DBCONN: LazyLock = LazyLock::new(|| { + trace::info!( + "Connecting to database \"{}\" using a pool of {} connections", + &config::SETTINGS.database.db_name, + &config::SETTINGS.database.max_pool_size + ); + + let db_uri = match config::SETTINGS.database.db_type.as_str() { + "mysql" | "postgres" => { + let mut tmp_uri = Url::parse( + format!( + "{}://{}/{}", + &config::SETTINGS.database.db_type, + &config::SETTINGS.database.db_host, + &config::SETTINGS.database.db_name + ) + .as_str(), + ) + .unwrap(); + tmp_uri + .set_username(config::SETTINGS.database.db_user.as_str()) + .unwrap(); + // https://github.com/launchbadge/sqlx/issues/1624 + tmp_uri + .set_password(Some(config::SETTINGS.database.db_pass.as_str())) + .unwrap(); + if let Some(port) = config::SETTINGS.database.db_port { + tmp_uri.set_port(Some(port)).unwrap(); + } + tmp_uri + } + "sqlite" => Url::parse( + format!( + "{}://{}", + &config::SETTINGS.database.db_type, + &config::SETTINGS.database.db_name + ) + .as_str(), + ) + .unwrap(), + _ => panic!( + "Unrecognized database type \"{}\"", + config::SETTINGS.database.db_type + ), + }; + + run_now(Database::connect::({ + let mut db_opt = ConnectOptions::new(db_uri.to_string()); + db_opt.max_connections(config::SETTINGS.database.max_pool_size); + db_opt + })) + .unwrap_or_else(|_| panic!("Failed to connect to database")) +}); + /// Implementa la extensión. pub struct SeaORM; @@ -122,6 +185,6 @@ impl Extension for SeaORM { } fn initialize(&self) { - std::sync::LazyLock::force(&db::DBCONN); + std::sync::LazyLock::force(&DBCONN); } } diff --git a/extensions/pagetop-seaorm/src/migration.rs b/extensions/pagetop-seaorm/src/migration.rs new file mode 100644 index 00000000..14b8f85b --- /dev/null +++ b/extensions/pagetop-seaorm/src/migration.rs @@ -0,0 +1,156 @@ +//! API para definir y ejecutar migraciones de base de datos. +//! +//! Re-exporta los tipos de SeaORM necesarios para escribir migraciones y ofrece las macros +//! [`crate::install_migrations`] y [`crate::uninstall_migrations`] para aplicarlas o revertirlas al +//! arrancar la extensión. +//! +//! ```rust,ignore +//! use pagetop_seaorm::db::*; +//! use pagetop_seaorm::migration::*; +//! ``` + +// **< Adaptación de `sea-orm-migration` (ver §Créditos en README.md) >***************************** + +//pub mod cli; +pub mod connection; +pub mod manager; +pub mod migrator; +//pub mod prelude; +pub mod schema; +pub mod seaql_migrations; +//pub mod util; + +pub use connection::*; +pub use manager::*; +//pub use migrator::*; + +pub use async_trait; +//pub use sea_orm; +//pub use sea_orm::sea_query; +pub use sea_orm::DbErr; + +pub trait MigrationName { + fn name(&self) -> &str; +} + +/// The migration definition +#[async_trait::async_trait] +pub trait MigrationTrait: MigrationName + Send + Sync { + /// Define actions to perform when applying the migration + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr>; + + /// Define actions to perform when rolling back the migration + async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { + Err(DbErr::Migration( + "Rollback not implemented for this migration".to_owned(), + )) + } +} + +// ************************************************************************************************* + +pub use migrator::MigratorTrait; +pub use schema::*; +pub use sea_orm::sea_query::*; +pub use sea_orm::DeriveIden; + +use pagetop::core::TypeInfo; +use pagetop::trace; + +impl MigrationName for M { + fn name(&self) -> &str { + TypeInfo::NameTo(-2).of::() + } +} + +pub type MigrationItem = Box; + +pub trait MigratorBase { + fn run_up(); + + fn run_down(); +} + +#[rustfmt::skip] +impl MigratorBase for M { + fn run_up() { + if let Err(e) = super::run_now(Self::up(SchemaManagerConnection::Connection(&super::DBCONN), None)) { + trace::error!("Migration upgrade failed ({})", e); + }; + } + + fn run_down() { + if let Err(e) = super::run_now(Self::down(SchemaManagerConnection::Connection(&super::DBCONN), None)) { + trace::error!("Migration downgrade failed ({})", e); + }; + } +} + +/// Aplica las migraciones pendientes al arrancar una extensión. +/// +/// Recibe uno o más módulos de migración y ejecuta el método `up` de los que aún no estén +/// registrados en la tabla `seaql_migrations`. Se invoca habitualmente desde +/// [`Extension::initialize`](pagetop::core::extension::Extension::initialize). +/// +/// ```rust,ignore +/// impl Extension for MyExt { +/// fn initialize(&self) { +/// install_migrations!( +/// m20240101_000001_create_users_table, +/// m20240115_000002_add_email_index, +/// ); +/// } +/// } +/// ``` +#[macro_export] +macro_rules! install_migrations { + ( $($migration_module:ident),+ $(,)? ) => {{ + use $crate::migration::{MigrationItem, MigratorBase, MigratorTrait}; + + struct Migrator; + impl MigratorTrait for Migrator { + fn migrations() -> Vec { + let mut m = Vec::::new(); + $( + m.push(Box::new(migration::$migration_module::Migration)); + )* + m + } + } + Migrator::run_up(); + }}; +} + +/// Revierte las migraciones de una extensión en orden inverso al de su aplicación. +/// +/// Ejecuta el método `down` de cada migración indicada. Si alguna no implementa `down`, +/// detiene el proceso con un error. Complementario a [`crate::install_migrations`]. +/// +/// ```rust,ignore +/// impl Extension for MyExt { +/// fn uninitialize(&self) { +/// uninstall_migrations!( +/// m20240101_000001_create_users_table, +/// m20240115_000002_add_email_index, +/// ); +/// } +/// } +/// ``` +#[macro_export] +macro_rules! uninstall_migrations { + ( $($migration_module:ident),+ $(,)? ) => {{ + use $crate::migration::{MigrationItem, MigratorBase, MigratorTrait}; + + struct Migrator; + impl MigratorTrait for Migrator { + fn migrations() -> Vec { + let mut m = Vec::::new(); + $( + m.push(Box::new(migration::$migration_module::Migration)); + )* + m + } + } + Migrator::run_down(); + }}; +} diff --git a/extensions/pagetop-seaorm/src/db/migration/connection.rs b/extensions/pagetop-seaorm/src/migration/connection.rs similarity index 100% rename from extensions/pagetop-seaorm/src/db/migration/connection.rs rename to extensions/pagetop-seaorm/src/migration/connection.rs diff --git a/extensions/pagetop-seaorm/src/db/migration/manager.rs b/extensions/pagetop-seaorm/src/migration/manager.rs similarity index 100% rename from extensions/pagetop-seaorm/src/db/migration/manager.rs rename to extensions/pagetop-seaorm/src/migration/manager.rs diff --git a/extensions/pagetop-seaorm/src/db/migration/migrator.rs b/extensions/pagetop-seaorm/src/migration/migrator.rs similarity index 100% rename from extensions/pagetop-seaorm/src/db/migration/migrator.rs rename to extensions/pagetop-seaorm/src/migration/migrator.rs diff --git a/extensions/pagetop-seaorm/src/db/migration/schema.rs b/extensions/pagetop-seaorm/src/migration/schema.rs similarity index 96% rename from extensions/pagetop-seaorm/src/db/migration/schema.rs rename to extensions/pagetop-seaorm/src/migration/schema.rs index 90910277..4dea5ee2 100644 --- a/extensions/pagetop-seaorm/src/db/migration/schema.rs +++ b/extensions/pagetop-seaorm/src/migration/schema.rs @@ -1,17 +1,16 @@ -//! Adapted from +//! Adaptación de //! -//! # Database Table Schema Helpers +//! # Ayudantes de esquema de base de datos //! -//! This module defines functions and helpers for creating database table -//! schemas using the `sea-orm` and `sea-query` libraries. +//! Define funciones y ayudantes para crear esquemas de tablas usando `sea-orm` y `sea-query`. //! -//! # Example +//! # Ejemplo //! -//! The following example shows how the user migration file should be and using -//! the schema helpers to create the Db fields. +//! El siguiente ejemplo muestra cómo escribir un archivo de migración usando los ayudantes +//! de esquema. //! //! ```rust -//! use pagetop_seaorm::db::*; +//! use pagetop_seaorm::migration::*; //! //! pub struct Migration; //! @@ -38,7 +37,7 @@ //! } //! } //! -//! #[derive(Iden)] +//! #[derive(DeriveIden)] //! pub enum Users { //! Table, //! Id, @@ -51,10 +50,9 @@ //! } //! ``` -use crate::db::Iden; - use sea_orm::sea_query::{ - self, Alias, ColumnDef, ColumnType, Expr, IntoIden, PgInterval, Table, TableCreateStatement, + self, Alias, ColumnDef, ColumnType, Expr, Iden, IntoIden, PgInterval, Table, + TableCreateStatement, }; #[derive(Iden)] @@ -599,7 +597,7 @@ pub fn array_uniq(col: T, elem_type: ColumnType) -> ColumnDef { array(col, elem_type).unique_key().take() } -/// Add timestamp columns (`CreatedAt` and `UpdatedAt`) to an existing table. +/// Añade las columnas de timestamp (`CreatedAt` y `UpdatedAt`) a una tabla existente. pub fn timestamps(t: TableCreateStatement) -> TableCreateStatement { let mut t = t; t.col(timestamp(GeneralIds::CreatedAt).default(Expr::current_timestamp())) @@ -607,7 +605,7 @@ pub fn timestamps(t: TableCreateStatement) -> TableCreateStatement { .take() } -/// Create an Alias. +/// Crea un alias. pub fn name>(name: T) -> Alias { Alias::new(name) } diff --git a/extensions/pagetop-seaorm/src/db/migration/seaql_migrations.rs b/extensions/pagetop-seaorm/src/migration/seaql_migrations.rs similarity index 100% rename from extensions/pagetop-seaorm/src/db/migration/seaql_migrations.rs rename to extensions/pagetop-seaorm/src/migration/seaql_migrations.rs