<div align="center">

<h1>PageTop SeaORM</h1>

<p>Proporciona a <strong>PageTop</strong> acceso basado en <a href="https://www.sea-ql.org/SeaORM">SeaORM</a> a bases de datos relacionales.</p>

[![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)

</div>

## 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`:

- Si estás desarrollando una **aplicación**, activa el motor de base de datos que necesites:

  ```toml
  [features]
  default  = ["postgres"]
  mysql    = ["pagetop-seaorm/mysql"]
  postgres = ["pagetop-seaorm/postgres"]
  sqlite   = ["pagetop-seaorm/sqlite"]

  [dependencies]
  pagetop-seaorm = { version = "..." }
  ```

  Las *features* disponibles son `mysql`, `postgres` y `sqlite`. Gracias a la unificación de
  *features* de Cargo, activar el motor aquí lo propaga a todos los *crates* del árbol de
  dependencias que usen `pagetop-seaorm`; no es necesario que cada extensión lo reexporte.

- Si estás escribiendo una **extensión**, declara la dependencia sin *features*:

  ```toml
  [dependencies]
  pagetop-seaorm = { version = "..." }
  ```

  La selección del motor es responsabilidad de la aplicación que use la extensión, no de la
  extensión misma.

**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` 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:

```rust,ignore
use pagetop::prelude::*;
use pagetop_seaorm::install_migrations;

mod migration;

struct MyApp;

#[async_trait]
impl Extension for MyApp {
    fn dependencies(&self) -> Vec<ExtensionRef> {
        vec![
            &pagetop_seaorm::SeaORM,
        ]
    }

    async fn initialize(&self) {
        install_migrations!(m20240101_000001_create_users);
    }
}

#[pagetop::main]
async fn main() -> std::io::Result<()> {
    Application::prepare(&MyApp).await.run().await
}
```

**Escribe las migraciones** usando la API de [`migration`]:

```rust,no_run
// src/migration/m20240101_000001_create_users.rs
use pagetop_seaorm::migration::*;

pub struct Migration;

#[pagetop::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))
                    .col(string(Users::Name))
                    .to_owned(),
            )
            .await
    }
}

#[derive(DeriveIden)]
enum Users {
    Table,
    Id,
    Email,
    Name,
}
```

**Define las entidades** en un módulo `entity/` usando las macros de derivación de [`db`]:

```rust,no_run
// src/entity/user.rs
use pagetop_seaorm::db::*;

#[derive(Clone, Debug, DeriveEntityModel, PartialEq)]
#[sea_orm(table_name = "users")]
pub struct Model {
    #[sea_orm(primary_key)]
    pub id: i32,
    pub email: String,
    pub name: String,
}

#[derive(Clone, Copy, Debug, DeriveRelation, EnumIter)]
pub enum Relation {}

impl ActiveModelBehavior for ActiveModel {}
```

**Opera con la base de datos** pasando la conexión [`db::dbconn()`] a cada consulta:

```rust,ignore
use pagetop_seaorm::db::*;

// Asumiendo que existe un módulo `user` con la entidad definida arriba.
async fn example() -> Result<(), DbErr> {
    // Listar todos los registros:
    let users = user::Entity::find().all(dbconn()).await?;

    // Buscar por clave primaria:
    let found = user::Entity::find_by_id(1).one(dbconn()).await?;

    // Insertar un registro:
    let new_user = user::ActiveModel {
        email: Set("alice@example.com".to_owned()),
        name: Set("Alice".to_owned()),
        ..Default::default()
    };
    user::Entity::insert(new_user).exec(dbconn()).await?;
    Ok(())
}
```

## 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).

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
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`              | 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`          | Absorbido en `migration.rs`, descarta exportaciones del CLI              |
| `schema.rs`           | Integra con ajustes, adaptado de [loco](https://github.com/loco-rs/loco) |
| `seaql_migrations.rs` | Integración completa                                                     |

## 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.
