diff --git a/Cargo.toml b/Cargo.toml index e1b8490b..bbdc6a3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -151,3 +151,6 @@ serde = { version = "1.0.185", features = ["derive"] } [package.metadata.docs.rs] # Have docs.rs pass `--all-features` to ensure all features have their documentation built. all-features = true + +[lints.rust] +unexpected_cfgs = { level = "allow", check-cfg = ['cfg(coverage_nightly)'] } diff --git a/src/migration/timestamp/mod.rs b/src/migration/timestamp/mod.rs index 84c8837a..86254fdd 100644 --- a/src/migration/timestamp/mod.rs +++ b/src/migration/timestamp/mod.rs @@ -30,11 +30,63 @@ pub mod m20240723_201404_add_update_timestamp_function; /// # } /// } /// ``` -pub async fn exec_create_update_timestamp_function( +pub async fn exec_create_update_timestamp_function( manager: &SchemaManager<'_>, - column: T, + timestamp_column: C, ) -> Result<(), DbErr> { - let statement = create_update_timestamp_function(manager, column); + let statement = create_update_timestamp_function(manager, timestamp_column); + if let Some(statement) = statement { + manager.get_connection().execute(statement).await?; + } + + Ok(()) +} + +/// Wrapper around [create_update_timestamp_function_dep_column] to execute the returned +/// [Statement], if present. +/// +/// # Examples +/// ```rust +/// use roadster::migration::schema::Timestamps; +/// use roadster::migration::timestamp::{exec_create_update_timestamp_function_dep_column}; +/// use sea_orm_migration::prelude::*; +/// +/// #[derive(DeriveMigrationName)] +/// pub struct Migration; +/// +/// #[derive(DeriveIden)] +/// pub(crate) enum User { +/// Table, +/// Name, +/// NameUpdatedAt +/// } +/// +/// const TIMESTAMP_COLUMN: User = User::NameUpdatedAt; +/// const DEPENDENT_COLUMN: User = User::Name; +/// +/// #[async_trait::async_trait] +/// impl MigrationTrait for Migration { +/// async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { +/// exec_create_update_timestamp_function_dep_column( +/// manager, +/// TIMESTAMP_COLUMN, +/// DEPENDENT_COLUMN, +/// ) +/// .await +/// } +/// # +/// # async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { +/// # todo!() +/// # } +/// } +/// ``` +pub async fn exec_create_update_timestamp_function_dep_column( + manager: &SchemaManager<'_>, + timestamp_column: C, + dep_column: D, +) -> Result<(), DbErr> { + let statement = + create_update_timestamp_function_dep_column(manager, timestamp_column, dep_column); if let Some(statement) = statement { manager.get_connection().execute(statement).await?; } @@ -46,22 +98,46 @@ pub async fn exec_create_update_timestamp_function( /// a [Statement] containing the SQL instructions to create the function. /// /// Note: Currently only supports Postgres. If another DB is used, will return [None]. -pub fn create_update_timestamp_function( +pub fn create_update_timestamp_function( manager: &SchemaManager<'_>, - column: T, + timestamp_column: C, ) -> Option { let backend = manager.get_database_backend(); - create_update_timestamp_function_for_db_backend(backend, column) + create_update_timestamp_function_for_db_backend(backend, timestamp_column) } -fn create_update_timestamp_function_for_db_backend( +/// Create a SQL function to update a timestamp column with the current timestamp, but only +/// if the provided dependent column is modified. Returns a [Statement] containing the SQL +/// instructions to create the function. +/// +/// Note: Currently only supports Postgres. If another DB is used, will return [None]. +pub fn create_update_timestamp_function_dep_column( + manager: &SchemaManager<'_>, + timestamp_column: C, + dep_column: D, +) -> Option { + let backend = manager.get_database_backend(); + create_update_timestamp_function_dep_column_for_db_backend( + backend, + timestamp_column, + dep_column, + ) +} + +/// Create a SQL function to update a timestamp column with the current timestamp. Returns +/// a [Statement] containing the SQL instructions to create the function. +/// +/// Note: Currently only supports Postgres. If another DB is used, will return [None]. +fn create_update_timestamp_function_for_db_backend( backend: DbBackend, - column: T, + timestamp_column: C, ) -> Option { if let DbBackend::Postgres = backend { let FnQueryStrings { - column, fn_call, .. - } = FnQueryStrings::new(column); + timestamp_column, + fn_call, + .. + } = FnQueryStrings::new::<_, C>(timestamp_column, None); let statement = Statement::from_string( backend, @@ -69,7 +145,48 @@ fn create_update_timestamp_function_for_db_backend( r#" CREATE OR REPLACE FUNCTION {fn_call} RETURNS TRIGGER AS $$ BEGIN - NEW.{column} = NOW(); + NEW.{timestamp_column} = NOW(); + RETURN NEW; +END; +$$ language 'plpgsql'; +"# + ), + ); + Some(statement) + } else { + None + } +} + +/// Create a SQL function to update a timestamp column with the current timestamp, but only +/// if the provided dependent column is modified. Returns a [Statement] containing the SQL +/// instructions to create the function. +/// +/// Note: Currently only supports Postgres. If another DB is used, will return [None]. +fn create_update_timestamp_function_dep_column_for_db_backend( + backend: DbBackend, + timestamp_column: C, + dep_column: D, +) -> Option { + if let DbBackend::Postgres = backend { + let FnQueryStrings { + timestamp_column, + dep_column, + fn_call, + .. + } = FnQueryStrings::new(timestamp_column, Some(dep_column)); + #[allow(clippy::expect_used)] + let dep_column = dep_column.expect("Dependent column should be present"); + + let statement = Statement::from_string( + backend, + format!( + r#" +CREATE OR REPLACE FUNCTION {fn_call} RETURNS TRIGGER AS $$ +BEGIN + IF OLD.{dep_column} IS DISTINCT FROM NEW.{dep_column} THEN + NEW.{timestamp_column} = NOW(); + END IF; RETURN NEW; END; $$ language 'plpgsql'; @@ -107,11 +224,11 @@ $$ language 'plpgsql'; /// } /// } /// ``` -pub async fn exec_drop_update_timestamp_function( +pub async fn exec_drop_update_timestamp_function( manager: &SchemaManager<'_>, - column: T, + timestamp_column: C, ) -> Result<(), DbErr> { - let statement = drop_update_timestamp_function(manager, column); + let statement = drop_update_timestamp_function(manager, timestamp_column); if let Some(statement) = statement { manager.get_connection().execute(statement).await?; } @@ -119,24 +236,25 @@ pub async fn exec_drop_update_timestamp_function( Ok(()) } -/// Drop a SQL function that was previously created by [create_update_timestamp_function]. -/// Returns a [Statement] containing the SQL instructions to create the function. +/// Drop a SQL function that was previously created by [`create_update_timestamp_function`] +/// or [`create_update_timestamp_function`]. Returns a [`Statement`] containing the SQL +/// instructions to drop the function. /// -/// Note: Currently only supports Postgres. If another DB is used, will return [None]. -pub fn drop_update_timestamp_function( +/// Note: Currently only supports Postgres. If another DB is used, will return [`None`]. +pub fn drop_update_timestamp_function( manager: &SchemaManager<'_>, - column: T, + timestamp_column: C, ) -> Option { let backend = manager.get_database_backend(); - drop_update_timestamp_function_for_db_backend(backend, column) + drop_update_timestamp_function_for_db_backend(backend, timestamp_column) } -fn drop_update_timestamp_function_for_db_backend( +fn drop_update_timestamp_function_for_db_backend( backend: DbBackend, - column: T, + timestamp_column: C, ) -> Option { if let DbBackend::Postgres = backend { - let FnQueryStrings { fn_name, .. } = FnQueryStrings::new(column); + let FnQueryStrings { fn_name, .. } = FnQueryStrings::new::<_, C>(timestamp_column, None); let statement = Statement::from_string(backend, format!(r#"DROP FUNCTION IF EXISTS {fn_name};"#)); @@ -181,9 +299,9 @@ fn drop_update_timestamp_function_for_db_backend( pub async fn exec_create_update_timestamp_trigger( manager: &SchemaManager<'_>, table: T, - column: C, + timestamp_column: C, ) -> Result<(), DbErr> { - let statement = create_update_timestamp_trigger(manager, table, column); + let statement = create_update_timestamp_trigger(manager, table, timestamp_column); if let Some(statement) = statement { manager.get_connection().execute(statement).await?; } @@ -199,23 +317,24 @@ pub async fn exec_create_update_timestamp_trigger( manager: &SchemaManager<'_>, table: T, - column: C, + timestamp_column: C, ) -> Option { let backend = manager.get_database_backend(); - create_update_timestamp_trigger_for_db_backend(backend, table, column) + create_update_timestamp_trigger_for_db_backend(backend, table, timestamp_column) } fn create_update_timestamp_trigger_for_db_backend( backend: DbBackend, table: T, - column: C, + timestamp_column: C, ) -> Option { if let DbBackend::Postgres = backend { let TriggerQueryNames { fn_query_strings: FnQueryStrings { fn_call, .. }, table, trigger_name, - } = TriggerQueryNames::new(table, column); + .. + } = TriggerQueryNames::new::<_, _, T>(table, timestamp_column, None); let statement = Statement::from_string( backend, @@ -270,9 +389,9 @@ EXECUTE PROCEDURE {fn_call}; pub async fn exec_drop_update_timestamp_trigger( manager: &SchemaManager<'_>, table: T, - column: C, + timestamp_column: C, ) -> Result<(), DbErr> { - let statement = drop_update_timestamp_trigger(manager, table, column); + let statement = drop_update_timestamp_trigger(manager, table, timestamp_column); if let Some(statement) = statement { manager.get_connection().execute(statement).await?; } @@ -287,23 +406,23 @@ pub async fn exec_drop_update_timestamp_trigger( manager: &SchemaManager<'_>, table: T, - column: C, + timestamp_column: C, ) -> Option { let backend = manager.get_database_backend(); - drop_update_timestamp_trigger_for_db_backend(backend, table, column) + drop_update_timestamp_trigger_for_db_backend(backend, table, timestamp_column) } fn drop_update_timestamp_trigger_for_db_backend( backend: DbBackend, table: T, - column: C, + timestamp_column: C, ) -> Option { if let DbBackend::Postgres = backend { let TriggerQueryNames { table, trigger_name, .. - } = TriggerQueryNames::new(table, column); + } = TriggerQueryNames::new::<_, _, T>(table, timestamp_column, None); let statement = Statement::from_string( backend, @@ -317,7 +436,8 @@ fn drop_update_timestamp_trigger_for_db_backend, fn_name: String, fn_call: String, } @@ -330,13 +450,15 @@ struct TriggerQueryNames { } impl FnQueryStrings { - fn new(column: C) -> Self { - let column = column.into_iden().to_string(); - let fn_name = update_timestamp_fn_name(&column); + fn new(timestamp_column: C, dep_column: Option) -> Self { + let timestamp_column = timestamp_column.into_iden().to_string(); + let dep_column = dep_column.map(|c| c.into_iden().to_string()); + let fn_name = update_timestamp_fn_name(×tamp_column); let fn_call = format!("{fn_name}()"); Self { - column, + timestamp_column, + dep_column, fn_name, fn_call, } @@ -344,8 +466,12 @@ impl FnQueryStrings { } impl TriggerQueryNames { - fn new(table: T, column: C) -> Self { - let fn_query_strings = FnQueryStrings::new(column); + fn new( + table: T, + timestamp_column: C, + dep_column: Option, + ) -> Self { + let fn_query_strings = FnQueryStrings::new(timestamp_column, dep_column); let table = table.into_iden().to_string(); let trigger_name = trigger_name(&table, &fn_query_strings.fn_name); @@ -378,6 +504,8 @@ mod tests { enum Foo { Table, UpdatedAt, + Password, + PasswordUpdatedAt, } #[fixture] @@ -427,6 +555,21 @@ mod tests { assert_debug_snapshot!(statement); } + #[rstest] + #[case(DbBackend::Postgres)] + #[case(DbBackend::MySql)] + #[case(DbBackend::Sqlite)] + #[cfg_attr(coverage_nightly, coverage(off))] + fn add_update_timestamp_function_dep_column(_case: TestCase, #[case] backend: DbBackend) { + let statement = super::create_update_timestamp_function_dep_column_for_db_backend( + backend, + Foo::PasswordUpdatedAt, + Foo::Password, + ); + + assert_debug_snapshot!(statement); + } + #[rstest] #[case(DbBackend::Postgres)] #[case(DbBackend::MySql)] @@ -442,14 +585,15 @@ mod tests { #[test] #[cfg_attr(coverage_nightly, coverage(off))] fn fn_query_strings() { - let fn_query_strings = FnQueryStrings::new(Foo::UpdatedAt); + let fn_query_strings = FnQueryStrings::new::<_, Foo>(Foo::UpdatedAt, None); assert_debug_snapshot!(fn_query_strings); } #[test] #[cfg_attr(coverage_nightly, coverage(off))] fn trigger_query_strings() { - let trigger_query_strings = TriggerQueryNames::new(Foo::Table, Foo::UpdatedAt); + let trigger_query_strings = + TriggerQueryNames::new::<_, _, Foo>(Foo::Table, Foo::UpdatedAt, None); assert_debug_snapshot!(trigger_query_strings); } } diff --git a/src/migration/timestamp/snapshots/roadster__migration__timestamp__tests__add_update_timestamp_function_dep_column@case_1.snap b/src/migration/timestamp/snapshots/roadster__migration__timestamp__tests__add_update_timestamp_function_dep_column@case_1.snap new file mode 100644 index 00000000..0aad3cae --- /dev/null +++ b/src/migration/timestamp/snapshots/roadster__migration__timestamp__tests__add_update_timestamp_function_dep_column@case_1.snap @@ -0,0 +1,11 @@ +--- +source: src/migration/timestamp/mod.rs +expression: statement +--- +Some( + Statement { + sql: "\nCREATE OR REPLACE FUNCTION update_timestamp_password_updated_at() RETURNS TRIGGER AS $$\nBEGIN\n IF OLD.password IS DISTINCT FROM NEW.password THEN\n NEW.password_updated_at = NOW();\n END IF;\n RETURN NEW;\nEND;\n$$ language 'plpgsql';\n", + values: None, + db_backend: Postgres, + }, +) diff --git a/src/migration/timestamp/snapshots/roadster__migration__timestamp__tests__add_update_timestamp_function_dep_column@case_2.snap b/src/migration/timestamp/snapshots/roadster__migration__timestamp__tests__add_update_timestamp_function_dep_column@case_2.snap new file mode 100644 index 00000000..50a9c1e4 --- /dev/null +++ b/src/migration/timestamp/snapshots/roadster__migration__timestamp__tests__add_update_timestamp_function_dep_column@case_2.snap @@ -0,0 +1,5 @@ +--- +source: src/migration/timestamp/mod.rs +expression: statement +--- +None diff --git a/src/migration/timestamp/snapshots/roadster__migration__timestamp__tests__add_update_timestamp_function_dep_column@case_3.snap b/src/migration/timestamp/snapshots/roadster__migration__timestamp__tests__add_update_timestamp_function_dep_column@case_3.snap new file mode 100644 index 00000000..50a9c1e4 --- /dev/null +++ b/src/migration/timestamp/snapshots/roadster__migration__timestamp__tests__add_update_timestamp_function_dep_column@case_3.snap @@ -0,0 +1,5 @@ +--- +source: src/migration/timestamp/mod.rs +expression: statement +--- +None diff --git a/src/migration/timestamp/snapshots/roadster__migration__timestamp__tests__fn_query_strings.snap b/src/migration/timestamp/snapshots/roadster__migration__timestamp__tests__fn_query_strings.snap index 38957ecf..d0ae600f 100644 --- a/src/migration/timestamp/snapshots/roadster__migration__timestamp__tests__fn_query_strings.snap +++ b/src/migration/timestamp/snapshots/roadster__migration__timestamp__tests__fn_query_strings.snap @@ -3,7 +3,8 @@ source: src/migration/timestamp/mod.rs expression: fn_query_strings --- FnQueryStrings { - column: "updated_at", + timestamp_column: "updated_at", + dep_column: None, fn_name: "update_timestamp_updated_at", fn_call: "update_timestamp_updated_at()", } diff --git a/src/migration/timestamp/snapshots/roadster__migration__timestamp__tests__trigger_query_strings.snap b/src/migration/timestamp/snapshots/roadster__migration__timestamp__tests__trigger_query_strings.snap index aee36544..daea92f4 100644 --- a/src/migration/timestamp/snapshots/roadster__migration__timestamp__tests__trigger_query_strings.snap +++ b/src/migration/timestamp/snapshots/roadster__migration__timestamp__tests__trigger_query_strings.snap @@ -4,7 +4,8 @@ expression: trigger_query_strings --- TriggerQueryNames { fn_query_strings: FnQueryStrings { - column: "updated_at", + timestamp_column: "updated_at", + dep_column: None, fn_name: "update_timestamp_updated_at", fn_call: "update_timestamp_updated_at()", }, diff --git a/src/migration/user/m20240724_005115_user_update_timestamp.rs b/src/migration/user/m20240724_005115_user_update_timestamp.rs index afc35bf5..47bb7614 100644 --- a/src/migration/user/m20240724_005115_user_update_timestamp.rs +++ b/src/migration/user/m20240724_005115_user_update_timestamp.rs @@ -13,12 +13,12 @@ use crate::migration::timestamp::{ use crate::migration::user::User; use sea_orm_migration::prelude::*; -#[derive(DeriveMigrationName)] -pub struct Migration; - const TABLE: User = User::Table; const COLUMN: Timestamps = Timestamps::UpdatedAt; +#[derive(DeriveMigrationName)] +pub struct Migration; + #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { diff --git a/src/migration/user/m20240729_000812_password_updated_at.rs b/src/migration/user/m20240729_000812_password_updated_at.rs new file mode 100644 index 00000000..108ea404 --- /dev/null +++ b/src/migration/user/m20240729_000812_password_updated_at.rs @@ -0,0 +1,57 @@ +//! Migration to add a [User::PasswordUpdatedAt] column to the `user` table. + +use crate::migration::user::User; +use sea_orm_migration::prelude::*; +use sea_orm_migration::schema::timestamp_with_time_zone; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.alter_table(add_column()).await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.alter_table(drop_column()).await + } +} + +fn add_column() -> TableAlterStatement { + Table::alter() + .table(User::Table) + .add_column( + timestamp_with_time_zone(User::PasswordUpdatedAt).default(Expr::current_timestamp()), + ) + .to_owned() +} + +fn drop_column() -> TableAlterStatement { + Table::alter() + .table(User::Table) + .drop_column(User::PasswordUpdatedAt) + .to_owned() +} + +#[cfg(test)] +mod tests { + use insta::assert_snapshot; + use sea_orm_migration::prelude::PostgresQueryBuilder; + + #[test] + #[cfg_attr(coverage_nightly, coverage(off))] + fn add_column() { + let stmt = super::add_column(); + + assert_snapshot!(stmt.to_string(PostgresQueryBuilder)); + } + + #[test] + #[cfg_attr(coverage_nightly, coverage(off))] + fn drop_column() { + let stmt = super::drop_column(); + + assert_snapshot!(stmt.to_string(PostgresQueryBuilder)); + } +} diff --git a/src/migration/user/m20240729_002549_password_updated_at_function.rs b/src/migration/user/m20240729_002549_password_updated_at_function.rs new file mode 100644 index 00000000..66306577 --- /dev/null +++ b/src/migration/user/m20240729_002549_password_updated_at_function.rs @@ -0,0 +1,32 @@ +//! Migration to create a SQL function to update the [User::PasswordUpdatedAt] column for a row +//! with the current timestamp, but only if the [User::Password] column was updated. +//! +//! Note: Currently only supports Postgres. If another DB is used, will do nothing. + +use crate::migration::timestamp::{ + exec_create_update_timestamp_function_dep_column, exec_drop_update_timestamp_function, +}; +use crate::migration::user::User; +use sea_orm_migration::prelude::*; + +const TIMESTAMP_COLUMN: User = User::PasswordUpdatedAt; +const DEPENDENT_COLUMN: User = User::Password; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + exec_create_update_timestamp_function_dep_column( + manager, + TIMESTAMP_COLUMN, + DEPENDENT_COLUMN, + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + exec_drop_update_timestamp_function(manager, TIMESTAMP_COLUMN).await + } +} diff --git a/src/migration/user/m20240729_002615_password_updated_at_trigger.rs b/src/migration/user/m20240729_002615_password_updated_at_trigger.rs new file mode 100644 index 00000000..aa4d0608 --- /dev/null +++ b/src/migration/user/m20240729_002615_password_updated_at_trigger.rs @@ -0,0 +1,29 @@ +//! Migration to create a SQL trigger to automatically update the [User::PasswordUpdatedAt] column +//! of a row in the `user` table whenever the row's [User::Password] column is updated. +//! +//! Expects to be run after [crate::migration::user::m20240729_002549_password_updated_at_function::Migration]. +//! +//! Note: Currently only supports Postgres. If another DB is used, will do nothing. + +use crate::migration::timestamp::{ + exec_create_update_timestamp_trigger, exec_drop_update_timestamp_trigger, +}; +use crate::migration::user::User; +use sea_orm_migration::prelude::*; + +const TABLE: User = User::Table; +const COLUMN: User = User::PasswordUpdatedAt; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + exec_create_update_timestamp_trigger(manager, TABLE, COLUMN).await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + exec_drop_update_timestamp_trigger(manager, TABLE, COLUMN).await + } +} diff --git a/src/migration/user/mod.rs b/src/migration/user/mod.rs index 06ea3ec2..7f64bddb 100644 --- a/src/migration/user/mod.rs +++ b/src/migration/user/mod.rs @@ -6,6 +6,9 @@ pub mod m20240714_203550_create_user_table_int_pk; pub mod m20240714_203551_create_user_table_uuid_pk; pub mod m20240723_070533_add_user_account_management_fields; pub mod m20240724_005115_user_update_timestamp; +pub mod m20240729_000812_password_updated_at; +mod m20240729_002549_password_updated_at_function; +mod m20240729_002615_password_updated_at_trigger; #[cfg(test)] mod tests; @@ -18,6 +21,15 @@ pub(crate) enum User { Username, Email, Password, + /// When the user's password was updated. Defaults to the[crate::migration::timestamp::Timestamps::UpdatedAt] + /// time. Useful in the event users' passwords may have been compromised and the application + /// needs to enforce that users update their passwords. + /// + /// Updated automatically when the [User::Password] is updated, assuming the following + /// migrations are applied: + /// 1. [m20240729_002549_password_updated_at_function::Migration] + /// 2. [m20240729_002615_password_updated_at_trigger::Migration] + PasswordUpdatedAt, EmailConfirmationSentAt, EmailConfirmationToken, EmailConfirmedAt, @@ -53,6 +65,9 @@ impl MigratorTrait for UserMigrator { Box::new(m20240723_070533_add_user_account_management_fields::Migration), Box::new(m20240723_201404_add_update_timestamp_function::Migration), Box::new(m20240724_005115_user_update_timestamp::Migration), + Box::new(m20240729_000812_password_updated_at::Migration), + Box::new(m20240729_002549_password_updated_at_function::Migration), + Box::new(m20240729_002615_password_updated_at_trigger::Migration), ] } } diff --git a/src/migration/user/snapshots/roadster__migration__user__m20240729_000812_password_updated_at__tests__add_column.snap b/src/migration/user/snapshots/roadster__migration__user__m20240729_000812_password_updated_at__tests__add_column.snap new file mode 100644 index 00000000..3e243299 --- /dev/null +++ b/src/migration/user/snapshots/roadster__migration__user__m20240729_000812_password_updated_at__tests__add_column.snap @@ -0,0 +1,5 @@ +--- +source: src/migration/user/m20240729_000812_password_updated_at.rs +expression: stmt.to_string(PostgresQueryBuilder) +--- +ALTER TABLE "user" ADD COLUMN "password_updated_at" timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP diff --git a/src/migration/user/snapshots/roadster__migration__user__m20240729_000812_password_updated_at__tests__drop_column.snap b/src/migration/user/snapshots/roadster__migration__user__m20240729_000812_password_updated_at__tests__drop_column.snap new file mode 100644 index 00000000..c949907e --- /dev/null +++ b/src/migration/user/snapshots/roadster__migration__user__m20240729_000812_password_updated_at__tests__drop_column.snap @@ -0,0 +1,5 @@ +--- +source: src/migration/user/m20240729_000812_password_updated_at.rs +expression: stmt.to_string(PostgresQueryBuilder) +--- +ALTER TABLE "user" DROP COLUMN "password_updated_at" diff --git a/src/migration/user/snapshots/roadster__migration__user__tests__user_migrator_migrations.snap b/src/migration/user/snapshots/roadster__migration__user__tests__user_migrator_migrations.snap index 7dbb5a07..b02828d3 100644 --- a/src/migration/user/snapshots/roadster__migration__user__tests__user_migrator_migrations.snap +++ b/src/migration/user/snapshots/roadster__migration__user__tests__user_migrator_migrations.snap @@ -7,4 +7,7 @@ expression: user_migrations "m20240723_070533_add_user_account_management_fields", "m20240723_201404_add_update_timestamp_function", "m20240724_005115_user_update_timestamp", + "m20240729_000812_password_updated_at", + "m20240729_002549_password_updated_at_function", + "m20240729_002615_password_updated_at_trigger", ]