rustbasic-core 0.1.16

Core framework logic for RustBasic - A modern web framework for Rust
Documentation
use sea_orm_migration::prelude::*;

pub struct Schema;

impl Schema {
    pub async fn create<F>(manager: &SchemaManager<'_>, table_name: &str, callback: F) -> Result<(), DbErr>
    where
        F: FnOnce(&mut Blueprint),
    {
        let mut blueprint = Blueprint::new(table_name);
        callback(&mut blueprint);
        
        // 1. Buat tabel
        manager.create_table(blueprint.table).await?;
        
        // 2. Buat semua index jika ada
        for index in blueprint.indices {
            manager.create_index(index).await?;
        }
        
        Ok(())
    }

    pub async fn drop(manager: &SchemaManager<'_>, table_name: &str) -> Result<(), DbErr> {
        manager.drop_table(
            Table::drop()
                .table(Alias::new(table_name))
                .to_owned()
        ).await
    }
}

pub struct Blueprint {
    pub table_name: String,
    pub table: TableCreateStatement,
    pub indices: Vec<IndexCreateStatement>,
}

impl Blueprint {
    pub fn new(table_name: &str) -> Self {
        let mut table = Table::create();
        table.table(Alias::new(table_name)).if_not_exists();
        Self {
            table_name: table_name.to_string(),
            table,
            indices: Vec::new(),
        }
    }

    pub fn id(&mut self) -> &mut Self {
        self.table.col(
            ColumnDef::new(Alias::new("id"))
                .integer()
                .not_null()
                .auto_increment()
                .primary_key()
        );
        self
    }

    pub fn string<'a>(&'a mut self, name: &str) -> ColumnBuilder<'a> {
        let mut col_def = ColumnDef::new(Alias::new(name));
        col_def.string();
        ColumnBuilder::new(self, name, col_def)
    }

    pub fn text<'a>(&'a mut self, name: &str) -> ColumnBuilder<'a> {
        let mut col_def = ColumnDef::new(Alias::new(name));
        col_def.text();
        ColumnBuilder::new(self, name, col_def)
    }

    pub fn integer<'a>(&'a mut self, name: &str) -> ColumnBuilder<'a> {
        let mut col_def = ColumnDef::new(Alias::new(name));
        col_def.integer();
        ColumnBuilder::new(self, name, col_def)
    }

    pub fn big_integer<'a>(&'a mut self, name: &str) -> ColumnBuilder<'a> {
        let mut col_def = ColumnDef::new(Alias::new(name));
        col_def.big_integer();
        ColumnBuilder::new(self, name, col_def)
    }

    pub fn float<'a>(&'a mut self, name: &str) -> ColumnBuilder<'a> {
        let mut col_def = ColumnDef::new(Alias::new(name));
        col_def.float();
        ColumnBuilder::new(self, name, col_def)
    }

    pub fn double<'a>(&'a mut self, name: &str) -> ColumnBuilder<'a> {
        let mut col_def = ColumnDef::new(Alias::new(name));
        col_def.double();
        ColumnBuilder::new(self, name, col_def)
    }

    pub fn decimal<'a>(&'a mut self, name: &str) -> ColumnBuilder<'a> {
        let mut col_def = ColumnDef::new(Alias::new(name));
        col_def.decimal();
        ColumnBuilder::new(self, name, col_def)
    }

    pub fn char<'a>(&'a mut self, name: &str) -> ColumnBuilder<'a> {
        let mut col_def = ColumnDef::new(Alias::new(name));
        col_def.char();
        ColumnBuilder::new(self, name, col_def)
    }

    pub fn boolean<'a>(&'a mut self, name: &str) -> ColumnBuilder<'a> {
        let mut col_def = ColumnDef::new(Alias::new(name));
        col_def.boolean();
        ColumnBuilder::new(self, name, col_def)
    }

    pub fn date_time<'a>(&'a mut self, name: &str) -> ColumnBuilder<'a> {
        let mut col_def = ColumnDef::new(Alias::new(name));
        col_def.date_time();
        ColumnBuilder::new(self, name, col_def)
    }

    pub fn timestamp<'a>(&'a mut self, name: &str) -> ColumnBuilder<'a> {
        let mut col_def = ColumnDef::new(Alias::new(name));
        col_def.timestamp();
        ColumnBuilder::new(self, name, col_def)
    }

    pub fn uuid<'a>(&'a mut self, name: &str) -> ColumnBuilder<'a> {
        let mut col_def = ColumnDef::new(Alias::new(name));
        col_def.uuid();
        ColumnBuilder::new(self, name, col_def)
    }

    pub fn json<'a>(&'a mut self, name: &str) -> ColumnBuilder<'a> {
        let mut col_def = ColumnDef::new(Alias::new(name));
        col_def.json();
        ColumnBuilder::new(self, name, col_def)
    }

    pub fn json_binary<'a>(&'a mut self, name: &str) -> ColumnBuilder<'a> {
        let mut col_def = ColumnDef::new(Alias::new(name));
        col_def.json_binary();
        ColumnBuilder::new(self, name, col_def)
    }

    pub fn binary<'a>(&'a mut self, name: &str) -> ColumnBuilder<'a> {
        let mut col_def = ColumnDef::new(Alias::new(name));
        col_def.binary();
        ColumnBuilder::new(self, name, col_def)
    }

    pub fn timestamps(&mut self) -> &mut Self {
        self.table.col(
            ColumnDef::new(Alias::new("created_at"))
                .date_time()
                .not_null()
                .default(Expr::current_timestamp())
        );
        self.table.col(
            ColumnDef::new(Alias::new("updated_at"))
                .date_time()
                .not_null()
                .default(Expr::current_timestamp())
        );
        self
    }

    pub fn foreign<'a>(&'a mut self, from_col: &str) -> ForeignKeyBuilder<'a> {
        ForeignKeyBuilder::new(self, from_col)
    }
}

pub struct ColumnBuilder<'a> {
    blueprint: &'a mut Blueprint,
    col_name: String,
    col_def: Option<ColumnDef>,
    is_indexed: bool,
}

impl<'a> ColumnBuilder<'a> {
    pub fn new(blueprint: &'a mut Blueprint, col_name: &str, col_def: ColumnDef) -> Self {
        Self {
            blueprint,
            col_name: col_name.to_string(),
            col_def: Some(col_def),
            is_indexed: false,
        }
    }

    pub fn not_null(mut self) -> Self {
        if let Some(ref mut col) = self.col_def {
            col.not_null();
        }
        self
    }

    pub fn nullable(mut self) -> Self {
        if let Some(ref mut col) = self.col_def {
            col.null();
        }
        self
    }

    pub fn unique(mut self) -> Self {
        if let Some(ref mut col) = self.col_def {
            col.unique_key();
        }
        self
    }

    pub fn primary_key(mut self) -> Self {
        if let Some(ref mut col) = self.col_def {
            col.primary_key();
        }
        self
    }

    pub fn default<T>(mut self, value: T) -> Self 
    where
        T: Into<SimpleExpr>,
    {
        if let Some(ref mut col) = self.col_def {
            col.default(value);
        }
        self
    }

    pub fn index(mut self) -> Self {
        self.is_indexed = true;
        self
    }
}

impl<'a> Drop for ColumnBuilder<'a> {
    fn drop(&mut self) {
        if let Some(mut col) = self.col_def.take() {
            self.blueprint.table.col(&mut col);
            if self.is_indexed {
                let index_name = format!("{}_{}_index", self.blueprint.table_name, self.col_name);
                let index_stmt = Index::create()
                    .name(&index_name)
                    .table(Alias::new(&self.blueprint.table_name))
                    .col(Alias::new(&self.col_name))
                    .to_owned();
                self.blueprint.indices.push(index_stmt);
            }
        }
    }
}

pub struct ForeignKeyBuilder<'a> {
    blueprint: &'a mut Blueprint,
    from_col: String,
    to_col: Option<String>,
    to_table: Option<String>,
    on_delete: Option<ForeignKeyAction>,
    on_update: Option<ForeignKeyAction>,
}

impl<'a> ForeignKeyBuilder<'a> {
    pub fn new(blueprint: &'a mut Blueprint, from_col: &str) -> Self {
        Self {
            blueprint,
            from_col: from_col.to_string(),
            to_col: None,
            to_table: None,
            on_delete: None,
            on_update: None,
        }
    }

    pub fn references(mut self, to_col: &str) -> Self {
        self.to_col = Some(to_col.to_string());
        self
    }

    pub fn on(mut self, to_table: &str) -> Self {
        self.to_table = Some(to_table.to_string());
        self
    }

    pub fn on_delete(mut self, action: &str) -> Self {
        let act = match action.to_lowercase().as_str() {
            "cascade" => ForeignKeyAction::Cascade,
            "restrict" => ForeignKeyAction::Restrict,
            "set null" | "set_null" => ForeignKeyAction::SetNull,
            "no action" | "no_action" => ForeignKeyAction::NoAction,
            _ => ForeignKeyAction::Cascade,
        };
        self.on_delete = Some(act);
        self
    }

    pub fn on_update(mut self, action: &str) -> Self {
        let act = match action.to_lowercase().as_str() {
            "cascade" => ForeignKeyAction::Cascade,
            "restrict" => ForeignKeyAction::Restrict,
            "set null" | "set_null" => ForeignKeyAction::SetNull,
            "no action" | "no_action" => ForeignKeyAction::NoAction,
            _ => ForeignKeyAction::Cascade,
        };
        self.on_update = Some(act);
        self
    }
}

impl<'a> Drop for ForeignKeyBuilder<'a> {
    fn drop(&mut self) {
        if let (Some(to_table), Some(to_col)) = (&self.to_table, &self.to_col) {
            let mut fk = ForeignKey::create();
            
            let fk_name = format!(
                "fk_{}_{}_{}_{}",
                self.blueprint.table_name, self.from_col, to_table, to_col
            );
            
            fk.name(&fk_name)
              .from(Alias::new(&self.blueprint.table_name), Alias::new(&self.from_col))
              .to(Alias::new(to_table), Alias::new(to_col));
              
            if let Some(action) = self.on_delete {
                fk.on_delete(action);
            }
            if let Some(action) = self.on_update {
                fk.on_update(action);
            }
            
            self.blueprint.table.foreign_key(&mut fk);
        }
    }
}