Corrige ejecución de "migrations" al desinst. mód.
This commit is contained in:
parent
a09f3aba7b
commit
842dd5234b
22 changed files with 644 additions and 71 deletions
|
|
@ -3,13 +3,19 @@ use pagetop::prelude::*;
|
|||
struct Drust;
|
||||
|
||||
impl AppTrait for Drust {
|
||||
fn enable_modules(&self) -> Vec<&'static dyn ModuleTrait> {
|
||||
fn enable_modules(&self) -> Vec<ModuleStaticRef> {
|
||||
vec![
|
||||
&pagetop_admin::Admin,
|
||||
&pagetop_user::User,
|
||||
&pagetop_node::Node,
|
||||
]
|
||||
}
|
||||
|
||||
fn disable_modules(&self) -> Vec<ModuleStaticRef> {
|
||||
vec![
|
||||
// &pagetop_node::Node,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ categories = [
|
|||
]
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.56"
|
||||
concat-string = "1.0.1"
|
||||
doc-comment = "0.3.3"
|
||||
figlet-rs = "0.1.3"
|
||||
|
|
@ -57,14 +58,13 @@ version = "0.9.1"
|
|||
features = ["debug-print", "macros", "runtime-async-std-native-tls"]
|
||||
default-features = false
|
||||
optional = true
|
||||
|
||||
[dependencies.sea-orm-migration]
|
||||
version = "0.9.1"
|
||||
[dependencies.sea-schema]
|
||||
version = "0.9.3"
|
||||
optional = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
database = ["sea-orm", "sea-orm-migration"]
|
||||
database = ["sea-orm", "sea-schema"]
|
||||
mysql = ["database", "sea-orm/sqlx-mysql"]
|
||||
postgres = ["database", "sea-orm/sqlx-postgres"]
|
||||
sqlite = ["database", "sea-orm/sqlx-sqlite"]
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ impl Application {
|
|||
#[cfg(feature = "database")]
|
||||
LazyStatic::force(&super::db::DBCONN);
|
||||
|
||||
// Deshabilita los módulos indicados por la aplicación.
|
||||
module::all::disable_modules(app.disable_modules());
|
||||
// Habilita los módulos predeterminados.
|
||||
module::all::enable_modules(vec![&base::module::homepage::DefaultHomePage]);
|
||||
// Habilita los módulos de la aplicación.
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
use crate::core::module::ModuleTrait;
|
||||
use crate::core::theme::ThemeTrait;
|
||||
use crate::core::module::ModuleStaticRef;
|
||||
use crate::core::theme::ThemeStaticRef;
|
||||
|
||||
pub trait AppTrait: Send + Sync {
|
||||
fn bootstrap(&self) {}
|
||||
|
||||
fn enable_modules(&self) -> Vec<&'static dyn ModuleTrait> {
|
||||
fn enable_modules(&self) -> Vec<ModuleStaticRef> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn disable_modules(&self) -> Vec<&'static dyn ModuleTrait> {
|
||||
fn disable_modules(&self) -> Vec<ModuleStaticRef> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn themes(&self) -> Vec<&'static dyn ThemeTrait> {
|
||||
fn themes(&self) -> Vec<ThemeStaticRef> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ pub struct Date {
|
|||
impl ComponentTrait for Date {
|
||||
fn new() -> Self {
|
||||
Date::default()
|
||||
.with_classes(ClassesOp::SetDefault,"form-item")
|
||||
.with_classes(ClassesOp::SetDefault, "form-item")
|
||||
.with_classes(ClassesOp::AddFirst, "form-type-date")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,8 +43,8 @@ impl ComponentTrait for MenuItem {
|
|||
|
||||
fn default_render(&self, context: &mut PageContext) -> Markup {
|
||||
match self.item_type() {
|
||||
MenuItemType::Void => html! {
|
||||
},
|
||||
MenuItemType::Void => html! {},
|
||||
|
||||
MenuItemType::Label(label) => html! {
|
||||
li class="label" { a href="#" { (label) } }
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ pub struct HtmlMarkup {
|
|||
|
||||
impl Default for HtmlMarkup {
|
||||
fn default() -> Self {
|
||||
HtmlMarkup {
|
||||
markup: html! {},
|
||||
}
|
||||
HtmlMarkup { markup: html! {} }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
mod definition;
|
||||
pub use definition::{BaseModule, ModuleTrait};
|
||||
pub use definition::{BaseModule, ModuleStaticRef, ModuleTrait};
|
||||
|
||||
pub(crate) mod all;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use super::ModuleTrait;
|
||||
use super::ModuleStaticRef;
|
||||
use crate::core::hook::add_action;
|
||||
use crate::{app, trace, LazyStatic};
|
||||
|
||||
|
|
@ -7,29 +7,39 @@ use crate::{db::*, run_now};
|
|||
|
||||
use std::sync::RwLock;
|
||||
|
||||
// Enabled modules.
|
||||
static ENABLED_MODULES: LazyStatic<RwLock<Vec<&dyn ModuleTrait>>> =
|
||||
// DISABLED MODULES ********************************************************************************
|
||||
|
||||
static DISABLED_MODULES: LazyStatic<RwLock<Vec<ModuleStaticRef>>> =
|
||||
LazyStatic::new(|| RwLock::new(Vec::new()));
|
||||
|
||||
/* Disabled modules.
|
||||
static DISABLED_MODULES: Lazy<RwLock<Vec<&dyn ModuleTrait>>> = Lazy::new(|| {
|
||||
RwLock::new(Vec::new())
|
||||
}); */
|
||||
|
||||
pub fn enable_modules(modules: Vec<&'static dyn ModuleTrait>) {
|
||||
for m in modules {
|
||||
enable(m)
|
||||
pub fn disable_modules(modules: Vec<ModuleStaticRef>) {
|
||||
let mut disabled_modules = DISABLED_MODULES.write().unwrap();
|
||||
for module in modules {
|
||||
if !disabled_modules
|
||||
.iter()
|
||||
.any(|m| m.handler() == module.handler())
|
||||
{
|
||||
trace::debug!("Disabling the \"{}\" module", module.single_name());
|
||||
disabled_modules.push(module);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn enable(module: &'static dyn ModuleTrait) {
|
||||
let mut list: Vec<&dyn ModuleTrait> = Vec::new();
|
||||
add_to(&mut list, module);
|
||||
list.reverse();
|
||||
ENABLED_MODULES.write().unwrap().append(&mut list);
|
||||
// ENABLED MODULES *********************************************************************************
|
||||
|
||||
static ENABLED_MODULES: LazyStatic<RwLock<Vec<ModuleStaticRef>>> =
|
||||
LazyStatic::new(|| RwLock::new(Vec::new()));
|
||||
|
||||
pub fn enable_modules(modules: Vec<ModuleStaticRef>) {
|
||||
for module in modules {
|
||||
let mut list: Vec<ModuleStaticRef> = Vec::new();
|
||||
add_to_enabled(&mut list, module);
|
||||
list.reverse();
|
||||
ENABLED_MODULES.write().unwrap().append(&mut list);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_to(list: &mut Vec<&dyn ModuleTrait>, module: &'static dyn ModuleTrait) {
|
||||
fn add_to_enabled(list: &mut Vec<ModuleStaticRef>, module: ModuleStaticRef) {
|
||||
if !ENABLED_MODULES
|
||||
.read()
|
||||
.unwrap()
|
||||
|
|
@ -37,21 +47,31 @@ fn add_to(list: &mut Vec<&dyn ModuleTrait>, module: &'static dyn ModuleTrait) {
|
|||
.any(|m| m.handler() == module.handler())
|
||||
&& !list.iter().any(|m| m.handler() == module.handler())
|
||||
{
|
||||
trace::debug!("Enabling module \"{}\"", module.single_name());
|
||||
list.push(module);
|
||||
if DISABLED_MODULES
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.any(|m| m.handler() == module.handler())
|
||||
{
|
||||
panic!(
|
||||
"Trying to enable \"{}\" module which is disabled",
|
||||
module.single_name()
|
||||
);
|
||||
} else {
|
||||
trace::debug!("Enabling the \"{}\" module", module.single_name());
|
||||
list.push(module);
|
||||
|
||||
let mut dependencies = module.dependencies();
|
||||
dependencies.reverse();
|
||||
for d in dependencies.iter() {
|
||||
add_to(list, *d);
|
||||
let mut dependencies = module.dependencies();
|
||||
dependencies.reverse();
|
||||
for d in dependencies.iter() {
|
||||
add_to_enabled(list, *d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
#[allow(unused_variables)]
|
||||
pub fn disable_module(module: &'static dyn ModuleTrait) {
|
||||
}
|
||||
*/
|
||||
|
||||
// CONFIGURE MODULES *******************************************************************************
|
||||
|
||||
pub fn modules(cfg: &mut app::web::ServiceConfig) {
|
||||
for m in ENABLED_MODULES.read().unwrap().iter() {
|
||||
m.configure_service(cfg);
|
||||
|
|
@ -68,6 +88,21 @@ pub fn register_actions() {
|
|||
|
||||
#[cfg(feature = "database")]
|
||||
pub fn run_migrations() {
|
||||
run_now({
|
||||
struct Migrator;
|
||||
impl MigratorTrait for Migrator {
|
||||
fn migrations() -> Vec<MigrationItem> {
|
||||
let mut migrations = vec![];
|
||||
for m in DISABLED_MODULES.read().unwrap().iter() {
|
||||
migrations.append(&mut m.migrations());
|
||||
}
|
||||
migrations
|
||||
}
|
||||
}
|
||||
Migrator::down(&app::db::DBCONN, None)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
run_now({
|
||||
struct Migrator;
|
||||
impl MigratorTrait for Migrator {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ use crate::util::{single_type_name, Handler};
|
|||
#[cfg(feature = "database")]
|
||||
use crate::db::MigrationItem;
|
||||
|
||||
pub type ModuleStaticRef = &'static dyn ModuleTrait;
|
||||
|
||||
pub trait BaseModule {
|
||||
fn single_name(&self) -> &'static str;
|
||||
}
|
||||
|
|
@ -21,7 +23,7 @@ pub trait ModuleTrait: BaseModule + Send + Sync {
|
|||
None
|
||||
}
|
||||
|
||||
fn dependencies(&self) -> Vec<&'static dyn ModuleTrait> {
|
||||
fn dependencies(&self) -> Vec<ModuleStaticRef> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
mod definition;
|
||||
pub use definition::{BaseTheme, ThemeTrait};
|
||||
pub use definition::{BaseTheme, ThemeStaticRef, ThemeTrait};
|
||||
|
||||
pub(crate) mod all;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use super::ThemeTrait;
|
||||
use super::ThemeStaticRef;
|
||||
use crate::{app, theme_static_files, trace, LazyStatic};
|
||||
|
||||
use std::sync::RwLock;
|
||||
|
|
@ -6,24 +6,23 @@ use std::sync::RwLock;
|
|||
include!(concat!(env!("OUT_DIR"), "/theme.rs"));
|
||||
|
||||
// Temas registrados.
|
||||
static THEMES: LazyStatic<RwLock<Vec<&dyn ThemeTrait>>> =
|
||||
static THEMES: LazyStatic<RwLock<Vec<ThemeStaticRef>>> =
|
||||
LazyStatic::new(|| RwLock::new(Vec::new()));
|
||||
|
||||
pub fn register_themes(themes: Vec<&'static dyn ThemeTrait>) {
|
||||
for t in themes {
|
||||
register(t)
|
||||
pub fn register_themes(themes: Vec<ThemeStaticRef>) {
|
||||
let mut registered_themes = THEMES.write().unwrap();
|
||||
for theme in themes {
|
||||
if !registered_themes
|
||||
.iter()
|
||||
.any(|t| t.handler() == theme.handler())
|
||||
{
|
||||
trace::debug!("Registering theme \"{}\"", theme.single_name());
|
||||
registered_themes.push(theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn register(theme: &'static dyn ThemeTrait) {
|
||||
let mut themes = THEMES.write().unwrap();
|
||||
if !themes.iter().any(|t| t.handler() == theme.handler()) {
|
||||
trace::debug!("Registering theme \"{}\"", theme.single_name());
|
||||
themes.push(theme);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn theme_by_single_name(single_name: &str) -> Option<&'static dyn ThemeTrait> {
|
||||
pub fn theme_by_single_name(single_name: &str) -> Option<ThemeStaticRef> {
|
||||
match THEMES
|
||||
.write()
|
||||
.unwrap()
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ use crate::html::{html, Favicon, Markup};
|
|||
use crate::response::page::{Page, PageContext, PageOp};
|
||||
use crate::util::{single_type_name, Handler};
|
||||
|
||||
pub type ThemeStaticRef = &'static dyn ThemeTrait;
|
||||
|
||||
pub trait BaseTheme {
|
||||
fn single_name(&self) -> &'static str;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,18 @@ pub use url::Url as DbUri;
|
|||
|
||||
pub use sea_orm::{DatabaseConnection as DbConn, ExecResult, QueryResult};
|
||||
|
||||
pub use sea_orm_migration::prelude::*;
|
||||
// El siguiente módulo migration es una versión simplificada del módulo sea_orm_migration (v0.9.1)
|
||||
// https://github.com/SeaQL/sea-orm/tree/0.9.1/sea-orm-migration para evitar los errores generados
|
||||
// por el paradigma modular de PageTop. Se copian los siguientes archivos del original:
|
||||
//
|
||||
// lib.rs => db/migration.rs (descartando el uso de algunos módulos y exportaciones)
|
||||
// manager.rs => db/migration/manager.rs
|
||||
// migrator.rs => db/migration/migrator.rs (suprimiendo la gestión de los errores)
|
||||
// prelude.rs => db/migration/prelude.rs (evitando cli)
|
||||
// seaql_migrations.rs => db/migration/seaql_migrations.rs
|
||||
//
|
||||
mod migration;
|
||||
pub use migration::prelude::*;
|
||||
|
||||
pub type MigrationItem = Box<dyn MigrationTrait>;
|
||||
|
||||
|
|
|
|||
30
pagetop/src/db/migration.rs
Normal file
30
pagetop/src/db/migration.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
//pub mod cli;
|
||||
pub mod manager;
|
||||
pub mod migrator;
|
||||
pub mod prelude;
|
||||
pub mod seaql_migrations;
|
||||
//pub mod util;
|
||||
|
||||
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()))
|
||||
}
|
||||
}
|
||||
133
pagetop/src/db/migration/manager.rs
Normal file
133
pagetop/src/db/migration/manager.rs
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
use sea_orm::sea_query::{
|
||||
extension::postgres::{TypeAlterStatement, TypeCreateStatement, TypeDropStatement},
|
||||
ForeignKeyCreateStatement, ForeignKeyDropStatement, IndexCreateStatement, IndexDropStatement,
|
||||
TableAlterStatement, TableCreateStatement, TableDropStatement, TableRenameStatement,
|
||||
TableTruncateStatement,
|
||||
};
|
||||
use sea_orm::{ConnectionTrait, DbBackend, DbConn, 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: &'c DbConn,
|
||||
}
|
||||
|
||||
impl<'c> SchemaManager<'c> {
|
||||
pub fn new(conn: &'c DbConn) -> Self {
|
||||
Self { conn }
|
||||
}
|
||||
|
||||
pub async fn exec_stmt<S>(&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) -> &'c DbConn {
|
||||
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<T>(&self, table: T) -> Result<bool, DbErr>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
let stmt = match self.conn.get_database_backend() {
|
||||
DbBackend::MySql => MySql::has_table(table),
|
||||
DbBackend::Postgres => Postgres::has_table(table),
|
||||
DbBackend::Sqlite => Sqlite::has_table(table),
|
||||
};
|
||||
|
||||
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 table exists".to_owned()))?;
|
||||
|
||||
res.try_get("", "has_table")
|
||||
}
|
||||
|
||||
pub async fn has_column<T, C>(&self, table: T, column: C) -> Result<bool, DbErr>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
C: AsRef<str>,
|
||||
{
|
||||
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")
|
||||
}
|
||||
}
|
||||
326
pagetop/src/db/migration/migrator.rs
Normal file
326
pagetop/src/db/migration/migrator.rs
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
use std::collections::HashSet;
|
||||
use std::fmt::Display;
|
||||
use std::time::SystemTime;
|
||||
use tracing::info;
|
||||
|
||||
use sea_orm::sea_query::{Alias, Expr, ForeignKey, Query, SelectStatement, SimpleExpr, Table};
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ActiveValue, ColumnTrait, Condition, ConnectionTrait, DbBackend, DbConn,
|
||||
DbErr, EntityTrait, QueryFilter, QueryOrder, Schema, Statement,
|
||||
};
|
||||
use sea_schema::{mysql::MySql, postgres::Postgres, probe::SchemaProbe, sqlite::Sqlite};
|
||||
|
||||
use super::{seaql_migrations, MigrationTrait, SchemaManager};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
/// Status of migration
|
||||
pub enum MigrationStatus {
|
||||
/// Not yet applied
|
||||
Pending,
|
||||
/// Applied
|
||||
Applied,
|
||||
}
|
||||
|
||||
pub struct Migration {
|
||||
migration: Box<dyn MigrationTrait>,
|
||||
status: MigrationStatus,
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Performing migrations on a database
|
||||
#[async_trait::async_trait]
|
||||
pub trait MigratorTrait: Send {
|
||||
/// Vector of migrations in time sequence
|
||||
fn migrations() -> Vec<Box<dyn MigrationTrait>>;
|
||||
|
||||
/// Get list of migrations wrapped in `Migration` struct
|
||||
fn get_migration_files() -> Vec<Migration> {
|
||||
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: &DbConn) -> Result<Vec<seaql_migrations::Model>, DbErr> {
|
||||
Self::install(db).await?;
|
||||
seaql_migrations::Entity::find()
|
||||
.order_by_asc(seaql_migrations::Column::Version)
|
||||
.all(db)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get list of migrations with status
|
||||
async fn get_migration_with_status(db: &DbConn) -> Result<Vec<Migration>, DbErr> {
|
||||
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<String> = migration_models
|
||||
.into_iter()
|
||||
.map(|model| model.version)
|
||||
.collect();
|
||||
let migration_in_fs: HashSet<String> = 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<String> = missing_migrations_in_fs
|
||||
.iter()
|
||||
.map(|missing_migration| {
|
||||
format!("Migration file of version '{}' is missing, this migration has been applied but its file is missing", missing_migration)
|
||||
}).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: &DbConn) -> Result<Vec<Migration>, DbErr> {
|
||||
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: &DbConn) -> Result<Vec<Migration>, DbErr> {
|
||||
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: &DbConn) -> Result<(), DbErr> {
|
||||
let builder = db.get_database_backend();
|
||||
let schema = Schema::new(builder);
|
||||
let mut stmt = schema.create_table_from_entity(seaql_migrations::Entity);
|
||||
stmt.if_not_exists();
|
||||
db.execute(builder.build(&stmt)).await.map(|_| ())
|
||||
}
|
||||
|
||||
/// Drop all tables from the database, then reapply all migrations
|
||||
async fn fresh(db: &DbConn) -> Result<(), DbErr> {
|
||||
Self::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 mut stmt = Query::select();
|
||||
stmt.columns([Alias::new("TABLE_NAME"), Alias::new("CONSTRAINT_NAME")])
|
||||
.from((
|
||||
Alias::new("information_schema"),
|
||||
Alias::new("table_constraints"),
|
||||
))
|
||||
.cond_where(
|
||||
Condition::all()
|
||||
.add(
|
||||
Expr::expr(get_current_schema(db)).equals(
|
||||
Alias::new("table_constraints"),
|
||||
Alias::new("table_schema"),
|
||||
),
|
||||
)
|
||||
.add(Expr::expr(Expr::value("FOREIGN KEY")).equals(
|
||||
Alias::new("table_constraints"),
|
||||
Alias::new("constraint_type"),
|
||||
)),
|
||||
);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
// 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
|
||||
Self::up(db, None).await
|
||||
}
|
||||
|
||||
/// Rollback all applied migrations, then reapply all migrations
|
||||
async fn refresh(db: &DbConn) -> Result<(), DbErr> {
|
||||
Self::down(db, None).await?;
|
||||
Self::up(db, None).await
|
||||
}
|
||||
|
||||
/// Rollback all applied migrations
|
||||
async fn reset(db: &DbConn) -> Result<(), DbErr> {
|
||||
Self::down(db, None).await
|
||||
}
|
||||
|
||||
/// Check the status of all migrations
|
||||
async fn status(db: &DbConn) -> Result<(), DbErr> {
|
||||
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(())
|
||||
}
|
||||
|
||||
/// Apply pending migrations
|
||||
async fn up(db: &DbConn, mut steps: Option<u32>) -> Result<(), DbErr> {
|
||||
Self::install(db).await?;
|
||||
let manager = SchemaManager::new(db);
|
||||
|
||||
if let Some(steps) = steps {
|
||||
info!("Applying {} pending migrations", steps);
|
||||
} else {
|
||||
info!("Applying all pending migrations");
|
||||
}
|
||||
|
||||
let migrations = Self::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::ActiveModel {
|
||||
version: ActiveValue::Set(migration.name().to_owned()),
|
||||
applied_at: ActiveValue::Set(now.as_secs() as i64),
|
||||
}
|
||||
.insert(db)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Rollback applied migrations
|
||||
async fn down(db: &DbConn, mut steps: Option<u32>) -> Result<(), DbErr> {
|
||||
Self::install(db).await?;
|
||||
let manager = SchemaManager::new(db);
|
||||
|
||||
if let Some(steps) = steps {
|
||||
info!("Rolling back {} applied migrations", steps);
|
||||
} else {
|
||||
info!("Rolling back all applied migrations");
|
||||
}
|
||||
|
||||
let migrations = Self::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(seaql_migrations::Column::Version.eq(migration.name()))
|
||||
.exec(db)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn query_tables(db: &DbConn) -> SelectStatement {
|
||||
match db.get_database_backend() {
|
||||
DbBackend::MySql => MySql::query_tables(),
|
||||
DbBackend::Postgres => Postgres::query_tables(),
|
||||
DbBackend::Sqlite => Sqlite::query_tables(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_current_schema(db: &DbConn) -> SimpleExpr {
|
||||
match db.get_database_backend() {
|
||||
DbBackend::MySql => MySql::get_current_schema(),
|
||||
DbBackend::Postgres => Postgres::get_current_schema(),
|
||||
DbBackend::Sqlite => unimplemented!(),
|
||||
}
|
||||
}
|
||||
10
pagetop/src/db/migration/prelude.rs
Normal file
10
pagetop/src/db/migration/prelude.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
//pub use super::cli;
|
||||
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::DbErr;
|
||||
pub use sea_orm::DeriveMigrationName;
|
||||
14
pagetop/src/db/migration/seaql_migrations.rs
Normal file
14
pagetop/src/db/migration/seaql_migrations.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||
#[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 {}
|
||||
|
|
@ -31,17 +31,23 @@ impl Favicon {
|
|||
}
|
||||
|
||||
pub fn with_theme_color(mut self, color: &str) -> Self {
|
||||
self.0.push(html! { meta name="theme-color" content=(color); });
|
||||
self.0.push(html! {
|
||||
meta name="theme-color" content=(color);
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_ms_tile_color(mut self, color: &str) -> Self {
|
||||
self.0.push(html! { meta name="msapplication-TileColor" content=(color); });
|
||||
self.0.push(html! {
|
||||
meta name="msapplication-TileColor" content=(color);
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_ms_tile_image(mut self, image: &str) -> Self {
|
||||
self.0.push(html! { meta name="msapplication-TileImage" content=(image); });
|
||||
self.0.push(html! {
|
||||
meta name="msapplication-TileImage" content=(image);
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
use super::PageOp;
|
||||
use crate::config::SETTINGS;
|
||||
use crate::core::theme::all::theme_by_single_name;
|
||||
use crate::core::theme::ThemeTrait;
|
||||
use crate::core::theme::{all::theme_by_single_name, ThemeStaticRef};
|
||||
use crate::html::{html, Assets, Favicon, IdentifierValue, JavaScript, Markup, ModeJS, StyleSheet};
|
||||
use crate::{base, concat_string, util, LazyStatic};
|
||||
|
||||
static DEFAULT_THEME: LazyStatic<&dyn ThemeTrait> =
|
||||
static DEFAULT_THEME: LazyStatic<ThemeStaticRef> =
|
||||
LazyStatic::new(|| match theme_by_single_name(&SETTINGS.app.theme) {
|
||||
Some(theme) => theme,
|
||||
None => &base::theme::bootsier::Bootsier,
|
||||
|
|
@ -13,7 +12,7 @@ static DEFAULT_THEME: LazyStatic<&dyn ThemeTrait> =
|
|||
|
||||
#[rustfmt::skip]
|
||||
pub struct PageContext {
|
||||
theme : &'static dyn ThemeTrait,
|
||||
theme : ThemeStaticRef,
|
||||
favicon : Option<Favicon>,
|
||||
metadata : Vec<(&'static str, &'static str)>,
|
||||
properties : Vec<(&'static str, &'static str)>,
|
||||
|
|
@ -94,7 +93,7 @@ impl PageContext {
|
|||
|
||||
/// PageContext GETTERS.
|
||||
|
||||
pub(crate) fn theme(&mut self) -> &'static dyn ThemeTrait {
|
||||
pub(crate) fn theme(&mut self) -> ThemeStaticRef {
|
||||
self.theme
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ mod mdbook;
|
|||
struct PageTopWebSite;
|
||||
|
||||
impl AppTrait for PageTopWebSite {
|
||||
fn enable_modules(&self) -> Vec<&'static dyn ModuleTrait> {
|
||||
fn enable_modules(&self) -> Vec<ModuleStaticRef> {
|
||||
vec![&mdbook::MdBook]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue