diff --git a/.env.linux.example b/.env.linux.example index c8d84aa..a4a56f4 100644 --- a/.env.linux.example +++ b/.env.linux.example @@ -1,2 +1,3 @@ SESAM_FSV_VERSION=1.40.13 SESAM_INI_PATH=/etc/opt/santesocial/fsv/${SESAM_FSV_VERSION}/conf/sesam.ini +DATABASE_URL=sqlite://p4pillon.sqlite?mode=rwc diff --git a/.env.win.example b/.env.win.example index bcd5177..68e50d8 100644 --- a/.env.win.example +++ b/.env.win.example @@ -1,2 +1,3 @@ SESAM_FSV_VERSION=1.40.13 SESAM_INI_PATH=${ALLUSERSPROFILE}\\santesocial\\fsv\\${SESAM_FSV_VERSION}\\conf\\sesam.ini +DATABASE_URL=sqlite://p4pillon.sqlite?mode=rwc diff --git a/.gitignore b/.gitignore index e238176..6ab57e3 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ target/ # Ignore .env files .env + +# Development Database +*.sqlite diff --git a/Cargo.toml b/Cargo.toml index a4ff5d4..f041158 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,6 @@ members = [ "crates/desktop", "crates/sesam-vitale", "crates/utils", + "migration", + "entity", ] diff --git a/README.md b/README.md index eb2ad78..05d7aec 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,24 @@ La CLI Tauri est nécessaire au lancement du client `desktop`. Elle peut être i cargo install tauri-cli --version "^2.0.0-rc" ``` +#### SeaORM CLI + +SeaORM est notre ORM. Le CLI SeaORM est nécessaire pour la génération des modèles de la base de données et des migrations associées. Elle peut être installée via Cargo : + +```bash +cargo install sea-orm-cli +``` + +L'applicatif va chercher les informations de connexion à la base de données dans la variable `DATABASE_URL` importée depuis les [fichiers de configuration](#fichiers-de-configuration). + +```.env +DATABASE_URL=sqlite://p4pillon.sqlite?mode=rwc +``` + +Toutefois, l'usage de la CLI de SeaORM nécessite de renseigner les informations de connexion à la base de données dans un fichier `.env` situé à la racine du projet. + +> Astuce : utilisé un lien symbolique pour éviter de dupliquer le fichier `.env`. + #### SESAM-Vitale La crate `sesam-vitale` nécessite la présence des librairies dynamiques fournies par le package FSV et la CryptolibCPS. Les instructions d'installation sont disponibles dans le [README](crates/sesam-vitale/README.md) de la crate `sesam-vitale`. @@ -75,3 +93,25 @@ Pour packager le client `desktop`, il est nécessaire de faire appel à la CLI T ```bash cargo tauri build ``` + +## Gestion de la base de données + +### Création d'une migration + +```bash +sea-orm-cli migrate generate +``` + +Cette commande génère un fichier de migration à adapter dans le dossier `migration/src`. + +### Appliquer les migrations + +```bash +sea-orm-cli migrate up +``` + +### Génération des entitées + +```bash +sea-orm-cli generate entity -o entity/src/entities +``` \ No newline at end of file diff --git a/crates/app/Cargo.toml b/crates/app/Cargo.toml index 73aa4ff..3d07cd0 100644 --- a/crates/app/Cargo.toml +++ b/crates/app/Cargo.toml @@ -8,14 +8,23 @@ askama = "0.12.1" askama_axum = "0.4.0" axum = "0.7.5" axum-htmx = { version = "0.6", features = ["auto-vary"] } +futures = "0.3.30" listenfd = "1.0.1" notify = "6.1.1" +sea-orm = { version = "1.0.1", features = [ + # Same as in the migration crate + "sqlx-sqlite", + "runtime-tokio-rustls", + "macros", +] } serde = { version = "1.0.204", features = ["derive"] } thiserror = "1.0.63" tokio = { version = "1.39.1", features = ["macros", "rt-multi-thread"] } tower-http = { version = "0.5.2", features = ["fs"] } tower-livereload = "0.9.3" +entity = { path = "../../entity" } +migration = { path = "../../migration" } utils = { path = "../utils" } [dev-dependencies] diff --git a/crates/app/README.md b/crates/app/README.md index f793935..3c5cf9b 100644 --- a/crates/app/README.md +++ b/crates/app/README.md @@ -2,6 +2,15 @@ - Récupérer le binaire TailwindCSS : https://tailwindcss.com/blog/standalone-cli +## Configuration + +> Astuce : lorsqu'on exécute directement la crate `App` à des fins de développement, le système de configuration n'utilisera pas l'éventuel fichier `.env` situé à la racine du workspace Rust. Pour éviter de dupliquer le fichier `.env`, il est possible de créer un lien symbolique vers le fichier `.env` de la crate `App` : + +```bash +cd crates/app +ln -s ../../.env .env +``` + ## Exécution - Lancer tailwindcss en mode watch dans un terminal : diff --git a/crates/app/src/db.rs b/crates/app/src/db.rs new file mode 100644 index 0000000..a50eb76 --- /dev/null +++ b/crates/app/src/db.rs @@ -0,0 +1,11 @@ +use migration::{Migrator, MigratorTrait}; +use sea_orm::{Database, DatabaseConnection, DbErr}; +use std::env; + +pub async fn get_connection() -> Result { + let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); + + let db_connection = Database::connect(database_url).await?; + Migrator::up(&db_connection, None).await?; + Ok(db_connection) +} diff --git a/crates/app/src/lib.rs b/crates/app/src/lib.rs index d7daf4c..a04e6ea 100644 --- a/crates/app/src/lib.rs +++ b/crates/app/src/lib.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use axum::http::{StatusCode, Uri}; use axum_htmx::AutoVaryLayer; +use sea_orm::DatabaseConnection; use thiserror::Error; use tower_http::services::ServeDir; @@ -27,12 +28,20 @@ pub fn init() -> Result<(), InitError> { Ok(()) } +#[derive(Clone)] +pub struct AppState { + db_connection: DatabaseConnection, +} pub async fn get_router(assets_path: PathBuf) -> axum::Router<()> { + let db_connection = db::get_connection().await.unwrap(); + let state: AppState = AppState { db_connection }; + axum::Router::new() .nest_service("/assets", ServeDir::new(assets_path)) .merge(pages::get_routes()) .fallback(fallback) + .with_state(state) // The AutoVaryLayer is used to avoid cache issues with htmx (cf: https://github.com/robertwayne/axum-htmx?tab=readme-ov-file#auto-caching-management) .layer(AutoVaryLayer) } diff --git a/crates/app/src/main.rs b/crates/app/src/main.rs index e60819d..e7ddc98 100644 --- a/crates/app/src/main.rs +++ b/crates/app/src/main.rs @@ -20,6 +20,8 @@ pub enum AppError { NotifyWatcher(#[from] notify::Error), #[error("Missing environment variable {var}")] MissingEnvVar { var: &'static str }, + #[error("Error with the database connection")] + DatabaseConnection(#[from] sea_orm::DbErr), #[error("Error while initialising the app")] Initialisation(#[from] InitError), } diff --git a/entity/Cargo.toml b/entity/Cargo.toml new file mode 100644 index 0000000..d7ae904 --- /dev/null +++ b/entity/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "entity" +version = "0.1.0" +edition = "2021" + +[lib] +name = "entity" +path = "src/lib.rs" + +[dependencies] +sea-orm = { version = "1.0.1" } diff --git a/entity/src/entities/debug.rs b/entity/src/entities/debug.rs new file mode 100644 index 0000000..0a0b0a9 --- /dev/null +++ b/entity/src/entities/debug.rs @@ -0,0 +1,17 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "debug")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub title: String, + pub text: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/entity/src/entities/mod.rs b/entity/src/entities/mod.rs new file mode 100644 index 0000000..e774aad --- /dev/null +++ b/entity/src/entities/mod.rs @@ -0,0 +1,5 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1 + +pub mod prelude; + +pub mod debug; diff --git a/entity/src/entities/prelude.rs b/entity/src/entities/prelude.rs new file mode 100644 index 0000000..bd169f7 --- /dev/null +++ b/entity/src/entities/prelude.rs @@ -0,0 +1,3 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1 + +pub use super::debug::Entity as Debug; diff --git a/entity/src/lib.rs b/entity/src/lib.rs new file mode 100644 index 0000000..21c129f --- /dev/null +++ b/entity/src/lib.rs @@ -0,0 +1,2 @@ +mod entities; +pub use entities::*; diff --git a/migration/Cargo.toml b/migration/Cargo.toml new file mode 100644 index 0000000..6b190a4 --- /dev/null +++ b/migration/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "migration" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "migration" +path = "src/lib.rs" + +[dependencies] +async-std = { version = "1", features = ["attributes", "tokio1"] } + +[dependencies.sea-orm-migration] +version = "1.0.0" +features = [ + # `ASYNC_RUNTIME` and `DATABASE_DRIVER` are required to run migration using the cli + # They must be the same as the features in the `sea-orm` dependency in the `app` crate + "sqlx-sqlite", # `DATABASE_DRIVER` feature + "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature +] diff --git a/migration/README.md b/migration/README.md new file mode 100644 index 0000000..3b438d8 --- /dev/null +++ b/migration/README.md @@ -0,0 +1,41 @@ +# Running Migrator CLI + +- Generate a new migration file + ```sh + cargo run -- generate MIGRATION_NAME + ``` +- Apply all pending migrations + ```sh + cargo run + ``` + ```sh + cargo run -- up + ``` +- Apply first 10 pending migrations + ```sh + cargo run -- up -n 10 + ``` +- Rollback last applied migrations + ```sh + cargo run -- down + ``` +- Rollback last 10 applied migrations + ```sh + cargo run -- down -n 10 + ``` +- Drop all tables from the database, then reapply all migrations + ```sh + cargo run -- fresh + ``` +- Rollback all applied migrations, then reapply all migrations + ```sh + cargo run -- refresh + ``` +- Rollback all applied migrations + ```sh + cargo run -- reset + ``` +- Check the status of all migrations + ```sh + cargo run -- status + ``` diff --git a/migration/src/lib.rs b/migration/src/lib.rs new file mode 100644 index 0000000..3f7859c --- /dev/null +++ b/migration/src/lib.rs @@ -0,0 +1,12 @@ +pub use sea_orm_migration::prelude::*; + +mod m20220101_000001_create_debug_table; + +pub struct Migrator; + +#[async_trait::async_trait] +impl MigratorTrait for Migrator { + fn migrations() -> Vec> { + vec![Box::new(m20220101_000001_create_debug_table::Migration)] + } +} diff --git a/migration/src/m20220101_000001_create_debug_table.rs b/migration/src/m20220101_000001_create_debug_table.rs new file mode 100644 index 0000000..f5f185c --- /dev/null +++ b/migration/src/m20220101_000001_create_debug_table.rs @@ -0,0 +1,35 @@ +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> { + manager + .create_table( + Table::create() + .table(Debug::Table) + .if_not_exists() + .col(pk_auto(Debug::Id)) + .col(string(Debug::Title)) + .col(string(Debug::Text)) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Debug::Table).to_owned()) + .await + } +} + +#[derive(DeriveIden)] +enum Debug { + Table, + Id, + Title, + Text, +} diff --git a/migration/src/main.rs b/migration/src/main.rs new file mode 100644 index 0000000..c6b6e48 --- /dev/null +++ b/migration/src/main.rs @@ -0,0 +1,6 @@ +use sea_orm_migration::prelude::*; + +#[async_std::main] +async fn main() { + cli::run_cli(migration::Migrator).await; +}