Añade migración de BD usando Refinery y Barrel

Realmente esta funcionalidad se va a sustituir por alguna otra librería
ya que Refinery usa un único número de versión que dificulta su uso en
un contexto de módulos independientes con migraciones propias.
This commit is contained in:
Manuel Cillero 2022-03-12 01:39:08 +01:00
parent 76785af4dc
commit 619b7b73c6
15 changed files with 129 additions and 62 deletions

View file

@ -29,7 +29,6 @@ once_cell = "1.9.0"
figlet-rs = "0.1.3" figlet-rs = "0.1.3"
config_rs = { package = "config", version = "0.11.0", features = ["toml"] } config_rs = { package = "config", version = "0.11.0", features = ["toml"] }
url = "2.2.2"
tracing = "0.1" tracing = "0.1"
tracing-appender = "0.2" tracing-appender = "0.2"
@ -44,24 +43,31 @@ actix-web-static-files = "3.0.5"
maud = { version = "0.23.0", features = ["actix-web"] } maud = { version = "0.23.0", features = ["actix-web"] }
sycamore = { version = "0.7.1", features = ["ssr"] } sycamore = { version = "0.7.1", features = ["ssr"] }
downcast-rs = "1.2.0" downcast-rs = "1.2.0"
url = "2.2.2"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
[dependencies.sea-orm] [dependencies.sqlx]
version = "0.6" version = "0.5.11"
features = ["macros", "debug-print", "runtime-async-std-native-tls"] features = ["migrate", "runtime-async-std-native-tls"]
default-features = false default-features = false
[dependencies.refinery]
version = "0.8.4"
[dependencies.barrel]
version = "0.7.0"
[features]
default = ["mysql"]
mysql = ["sqlx/mysql", "refinery/mysql", "barrel/mysql"]
postgres = ["sqlx/postgres", "refinery/postgres", "barrel/pg"]
[build-dependencies] [build-dependencies]
actix-web-static-files = "3.0.5" actix-web-static-files = "3.0.5"
[features]
default = ["sea-orm/sqlx-mysql"]
mysql = ["sea-orm/sqlx-mysql"]
postgres = ["sea-orm/sqlx-postgres"]
sqlite = ["sea-orm/sqlx-sqlite"]
[lib] [lib]
name = "pagetop" name = "pagetop"

View file

@ -10,3 +10,4 @@ edition = "2021"
pagetop = { path = "pagetop" } pagetop = { path = "pagetop" }
actix-web = "3.3.3" actix-web = "3.3.3"
maud = { version = "0.23.0" } maud = { version = "0.23.0" }
serde = { version = "1.0", features = ["derive"] }

View file

@ -0,0 +1,13 @@
use crate::prelude::*;
pub fn migration() -> String {
let mut m = db::Migration::new();
m.create_table("system", |t| {
t.add_column("id", db::types::primary());
t.add_column("title", db::types::varchar(255));
t.add_column("is_completed", db::types::boolean().default(false));
});
m.make::<db::Database>()
}

View file

@ -1,6 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
localize!("en-US", "src/base/module/admin/locales"); localize!("en-US", "src/base/module/admin/locales");
embed_migrations!("src/base/module/admin/migrations");
mod summary; mod summary;
@ -25,4 +26,8 @@ impl Module for AdminModule {
.route("", server::web::get().to(summary::summary)) .route("", server::web::get().to(summary::summary))
); );
} }
fn configure_migrations(&self) -> Option<db::Migrations> {
Some(migrations::runner())
}
} }

View file

@ -0,0 +1,13 @@
use crate::prelude::*;
pub fn migration() -> String {
let mut m = db::Migration::new();
m.create_table("user", |t| {
t.add_column("id", db::types::primary());
t.add_column("title", db::types::varchar(255));
t.add_column("is_completed", db::types::boolean().default(false));
});
m.make::<db::Database>()
}

View file

@ -1,6 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
localize!("en-US", "src/base/module/user/locales"); localize!("en-US", "src/base/module/user/locales");
embed_migrations!("src/base/module/user/migrations");
pub struct UserModule; pub struct UserModule;
@ -20,6 +21,10 @@ impl Module for UserModule {
fn configure_module(&self, cfg: &mut server::web::ServiceConfig) { fn configure_module(&self, cfg: &mut server::web::ServiceConfig) {
cfg.route("/user/login", server::web::get().to(login)); cfg.route("/user/login", server::web::get().to(login));
} }
fn configure_migrations(&self) -> Option<db::Migrations> {
Some(migrations::runner())
}
} }
fn form_login() -> impl PageComponent { fn form_login() -> impl PageComponent {

View file

@ -1,4 +1,4 @@
use crate::Lazy; use crate::{Lazy, db};
use crate::core::theme::Theme; use crate::core::theme::Theme;
use crate::core::module::Module; use crate::core::module::Module;
use crate::core::response::page::PageContainer; use crate::core::response::page::PageContainer;
@ -42,6 +42,18 @@ pub fn modules(cfg: &mut server::web::ServiceConfig) {
} }
} }
pub fn migrations(db_uri: db::Uri) {
let mut conn = refinery::config::Config::try_from(db_uri).unwrap();
for m in MODULES.read().unwrap().iter() {
match m.configure_migrations() {
Some(migrations) => {
migrations.run(&mut conn).expect("Failed to run migrations");
},
_ => {}
};
}
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Componentes globales. // Componentes globales.
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------

View file

@ -1,3 +1,4 @@
use crate::db;
use crate::core::server; use crate::core::server;
/// Los módulos deben implementar este *trait*. /// Los módulos deben implementar este *trait*.
@ -13,4 +14,8 @@ pub trait Module: Send + Sync {
#[allow(unused_variables)] #[allow(unused_variables)]
fn configure_module(&self, cfg: &mut server::web::ServiceConfig) { fn configure_module(&self, cfg: &mut server::web::ServiceConfig) {
} }
fn configure_migrations(&self) -> Option<db::Migrations> {
None
}
} }

View file

@ -1,12 +1,23 @@
use crate::{Lazy, base, locale, trace}; use crate::{Lazy, base, db, locale, trace};
use crate::config::SETTINGS; use crate::config::SETTINGS;
use crate::core::{Server, global, server}; use crate::core::{Server, global, server};
use crate::core::theme::register_theme; use crate::core::theme::register_theme;
use crate::core::module::register_module; use crate::core::module::register_module;
use std::io::Error; use std::io::Error;
use std::sync::RwLock;
use actix_web::middleware::normalize::{NormalizePath, TrailingSlash}; use actix_web::middleware::normalize::{NormalizePath, TrailingSlash};
#[cfg(feature = "mysql")]
use sqlx::mysql::MySqlPoolOptions as DbPoolOptions;
#[cfg(feature = "postgres")]
use sqlx::postgres::PgPoolOptions as DbPoolOptions;
static DBCONN: Lazy<RwLock<Option<db::Conn>>> = Lazy::new(|| {
RwLock::new(None)
});
pub struct Application { pub struct Application {
server: Server, server: Server,
} }
@ -52,49 +63,33 @@ impl Application {
&SETTINGS.database.max_pool_size &SETTINGS.database.max_pool_size
); );
#[cfg(any(feature = "default", feature = "mysql"))] #[cfg(feature = "mysql")]
let db_uri = format!( let db_type = "mysql";
"mysql://{}/{}",
&SETTINGS.database.db_host,
&SETTINGS.database.db_name
);
#[cfg(feature = "postgres")] #[cfg(feature = "postgres")]
let db_uri = format!( let db_type = "postgres";
"postgres://{}/{}",
&SETTINGS.database.db_host,
&SETTINGS.database.db_name
);
#[cfg(feature = "sqlite")]
let db_uri = format!("sqlite://{}", &SETTINGS.database.db_name);
let mut uri = url::Url::parse(&db_uri).unwrap();
// https://github.com/launchbadge/sqlx/issues/1624 // https://github.com/launchbadge/sqlx/issues/1624
let mut db_uri = db::Uri::parse(format!(
#[cfg(not(feature = "sqlite"))] "{}://{}/{}",
uri.set_username(&SETTINGS.database.db_user.as_str()).unwrap(); db_type,
&SETTINGS.database.db_host,
#[cfg(not(feature = "sqlite"))] &SETTINGS.database.db_name
uri.set_password(Some(&SETTINGS.database.db_pass.as_str())).unwrap(); ).as_str()).unwrap();
db_uri.set_username(&SETTINGS.database.db_user.as_str()).unwrap();
#[cfg(not(feature = "sqlite"))] db_uri.set_password(Some(&SETTINGS.database.db_pass.as_str())).unwrap();
if SETTINGS.database.db_port != 0 { if SETTINGS.database.db_port != 0 {
uri.set_port(Some(SETTINGS.database.db_port)).unwrap(); db_uri.set_port(Some(SETTINGS.database.db_port)).unwrap();
} }
let mut db_options = sea_orm::ConnectOptions::new(uri.to_string()); let db_pool = DbPoolOptions::new()
db_options.max_connections(SETTINGS.database.max_pool_size); .max_connections(SETTINGS.database.max_pool_size)
.connect(db_uri.as_str())
let mut db_conn = server::dbconn::DBCONN.write().unwrap();
*db_conn = Some(
sea_orm::Database::connect::<sea_orm::ConnectOptions>(
db_options.into()
)
.await .await
.expect("Failed to connect to database") .expect("Failed to connect to database");
);
let mut dbconn = DBCONN.write().unwrap();
*dbconn = Some(db_pool);
// Registra los temas predefinidos. // Registra los temas predefinidos.
register_theme(&base::theme::aliner::AlinerTheme); register_theme(&base::theme::aliner::AlinerTheme);
@ -107,7 +102,7 @@ impl Application {
// Ejecuta la función de inicio de la aplicación. // Ejecuta la función de inicio de la aplicación.
if bootstrap != None { if bootstrap != None {
trace::debug!("Calling application bootstrap"); trace::info!("Calling application bootstrap.");
let _ = &(bootstrap.unwrap())(); let _ = &(bootstrap.unwrap())();
} }
@ -115,6 +110,10 @@ impl Application {
// Al ser el último, puede sobrecargarse con la función de inicio. // Al ser el último, puede sobrecargarse con la función de inicio.
register_module(&base::module::homepage::HomepageModule); register_module(&base::module::homepage::HomepageModule);
// Run migrations.
trace::info!("Running migrations.");
global::migrations(db_uri);
// Prepara el servidor web. // Prepara el servidor web.
let server = server::HttpServer::new(|| { let server = server::HttpServer::new(|| {
server::App::new() server::App::new()

View file

@ -1,8 +0,0 @@
use crate::Lazy;
use crate::database::DatabaseConnection;
use std::sync::RwLock;
pub static DBCONN: Lazy<RwLock<Option<DatabaseConnection>>> = Lazy::new(|| {
RwLock::new(None)
});

View file

@ -4,7 +4,5 @@ pub use actix_web::{
mod tracing; mod tracing;
mod dbconn;
mod app; mod app;
pub use app::Application; pub use app::Application;

View file

@ -1 +0,0 @@
pub use sea_orm::DatabaseConnection;

17
src/db.rs Normal file
View file

@ -0,0 +1,17 @@
pub use url::Url as Uri;
#[cfg(feature = "mysql")]
pub use {
barrel::backend::MySql as Database,
sqlx::MySqlPool as Conn,
};
#[cfg(feature = "postgres")]
pub use {
barrel::backend::Pg as Database,
sqlx::PgPool as Conn,
};
pub use barrel::{Migration, types};
pub use refinery::embed_migrations;
pub use refinery::Runner as Migrations;

View file

@ -10,8 +10,8 @@ pub use once_cell::sync::Lazy;
pub mod config; // Gestión de la configuración. pub mod config; // Gestión de la configuración.
pub mod trace; // Registro de trazas y eventos de la aplicación. pub mod trace; // Registro de trazas y eventos de la aplicación.
pub mod locale; // Localización. pub mod locale; // Localización.
pub mod database; // Acceso a la base de datos. pub mod db; // Acceso a la base de datos.
pub mod core; // Servidor web y sistemas para Temas, Módulos y Respuestas. pub mod core; // Servidor web y APIs para Temas, Módulos y Respuestas web.
pub mod base; // Temas, Módulos y Componentes base. pub mod base; // Temas, Módulos y Componentes base.
pub mod util; // Macros y funciones útiles. pub mod util; // Macros y funciones útiles.

View file

@ -6,7 +6,9 @@ pub use crate::util;
pub use crate::config::SETTINGS; pub use crate::config::SETTINGS;
pub use crate::trace; pub use crate::trace;
pub use crate::localize; pub use crate::localize;
pub use crate::database;
pub use crate::db;
pub use crate::db::embed_migrations;
pub use crate::core::theme::*; pub use crate::core::theme::*;
pub use crate::core::module::*; pub use crate::core::module::*;