docbox_database/migrations/
mod.rs

1use crate::{
2    DbResult, DbTransaction,
3    models::{
4        tenant::Tenant,
5        tenant_migration::{CreateTenantMigration, TenantMigration},
6    },
7};
8use chrono::Utc;
9use std::ops::DerefMut;
10
11const TENANT_MIGRATIONS: &[(&str, &str)] = &[
12    (
13        "m1_create_users_table",
14        include_str!("./tenant/m1_create_users_table.sql"),
15    ),
16    (
17        "m2_create_document_box_table",
18        include_str!("./tenant/m2_create_document_box_table.sql"),
19    ),
20    (
21        "m3_create_folders_table",
22        include_str!("./tenant/m3_create_folders_table.sql"),
23    ),
24    (
25        "m4_create_files_table",
26        include_str!("./tenant/m4_create_files_table.sql"),
27    ),
28    (
29        "m5_create_generated_files_table",
30        include_str!("./tenant/m5_create_generated_files_table.sql"),
31    ),
32    (
33        "m6_create_links_table",
34        include_str!("./tenant/m6_create_links_table.sql"),
35    ),
36    (
37        "m7_create_edit_history_table",
38        include_str!("./tenant/m7_create_edit_history_table.sql"),
39    ),
40    (
41        "m8_create_tasks_table",
42        include_str!("./tenant/m8_create_tasks_table.sql"),
43    ),
44    (
45        "m9_create_presigned_upload_tasks_table",
46        include_str!("./tenant/m9_create_presigned_upload_tasks_table.sql"),
47    ),
48];
49
50/// Applies migrations to the provided tenant, only applies migrations that
51/// haven't already been applied
52///
53/// Optionally filtered to a specific migration through `target_migration_name`
54pub async fn apply_tenant_migrations(
55    root_t: &mut DbTransaction<'_>,
56    t: &mut DbTransaction<'_>,
57    tenant: &Tenant,
58    target_migration_name: Option<&str>,
59) -> DbResult<()> {
60    let migrations =
61        TenantMigration::find_by_tenant(root_t.deref_mut(), tenant.id, &tenant.env).await?;
62
63    for (migration_name, migration) in TENANT_MIGRATIONS {
64        // If targeting a specific migration only apply the target one
65        if target_migration_name
66            .is_some_and(|target_migration_name| target_migration_name.ne(*migration_name))
67        {
68            continue;
69        }
70
71        // Skip already applied migrations
72        if migrations
73            .iter()
74            .any(|migration| migration.name.eq(migration_name))
75        {
76            continue;
77        }
78
79        // Apply the migration
80        apply_tenant_migration(t, migration_name, migration).await?;
81
82        // Store the applied migration
83        TenantMigration::create(
84            root_t.deref_mut(),
85            CreateTenantMigration {
86                tenant_id: tenant.id,
87                env: tenant.env.clone(),
88                name: migration_name.to_string(),
89                applied_at: Utc::now(),
90            },
91        )
92        .await?;
93    }
94
95    Ok(())
96}
97/// Applies migrations without checking if migrations have already been applied
98///
99/// Should only be used for integration tests where you aren't setting up the root database
100pub async fn force_apply_tenant_migrations(
101    t: &mut DbTransaction<'_>,
102    target_migration_name: Option<&str>,
103) -> DbResult<()> {
104    for (migration_name, migration) in TENANT_MIGRATIONS {
105        // If targeting a specific migration only apply the target one
106        if target_migration_name
107            .is_some_and(|target_migration_name| target_migration_name.ne(*migration_name))
108        {
109            continue;
110        }
111
112        apply_tenant_migration(t, migration_name, migration).await?;
113    }
114
115    Ok(())
116}
117
118/// Apply a migration to the specific tenant database
119async fn apply_tenant_migration(
120    db: &mut DbTransaction<'_>,
121    migration_name: &str,
122    migration: &str,
123) -> DbResult<()> {
124    // Split the SQL queries into multiple queries
125    let queries = migration
126        .split(';')
127        .map(|query| query.trim())
128        .filter(|query| !query.is_empty());
129
130    for query in queries {
131        let result = sqlx::query(query)
132            .execute(db.deref_mut())
133            .await
134            .inspect_err(|error| {
135                tracing::error!(?error, ?migration_name, "failed to perform migration")
136            })?;
137        let rows_affected = result.rows_affected();
138
139        tracing::debug!(?migration_name, ?rows_affected, "applied migration query");
140    }
141
142    Ok(())
143}