# How Meetily Detects and Handles Legacy Database Migrations

> Discover how Meetily effortlessly detects and handles legacy database migrations. Learn about automatic detection, data copying, schema migrations, and WAL recovery for seamless, data-loss-free upgrades.

- Repository: [Zackriya Solutions/meetily](https://github.com/Zackriya-Solutions/meetily)
- Tags: migration-guide
- Published: 2026-07-18

---

**Meetily automatically detects legacy FastAPI databases by checking for the presence of `meeting_minutes.db` at startup, copies the data to the new `meeting_minutes.sqlite` format, applies pending schema migrations via SQLx, and includes WAL corruption recovery to ensure seamless upgrades without data loss.**

Meetily is an open-source meeting minutes application built with **Tauri** that transitioned from a FastAPI backend to a local-first **SQLite** architecture. When users upgrade from earlier versions utilizing the legacy Python backend, the Rust-based database manager must detect and migrate existing data without requiring manual intervention. This article examines the detection logic, file handling, and migration workflow implemented in [`frontend/src-tauri/src/database/manager.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/database/manager.rs).

## Automatic Legacy Database Detection

### Path Resolution Strategy

The `new_from_app_handle` function resolves the application data directory and constructs two distinct file paths during initialization. It checks for both the current Tauri database (`meeting_minutes.sqlite`) and the legacy FastAPI database (`meeting_minutes.db`) according to lines 55‑63 of the source:

```rust
let tauri_db_path = app_data_dir.join("meeting_minutes.sqlite");
let backend_db_path = app_data_dir.join("meeting_minutes.db");

```

### Conditional File Copying

When the SQLite file does not exist but the legacy `.db` file is present, the system automatically copies the legacy database to the new filename before establishing a connection. This logic at lines 19‑27 prevents data loss while transitioning to the Tauri architecture:

```rust
if !Path::new(tauri_db_path).exists() {
    if Path::new(backend_db_path).exists() {
        log::info!("Copying database from {} to {}", backend_db_path, tauri_db_path);
        fs::copy(backend_db_path, tauri_db_path)?;
    } else {
        // create fresh DB
    }
}

```

## Migration and Recovery Workflow

### Schema Migration with SQLx

After preparing the database file, the code applies pending migrations using the SQLx macro. This loads SQL scripts from the `frontend/src-tauri/migrations/` directory and upgrades the legacy schema to the current version (line 35):

```rust
sqlx::migrate!("./migrations").run(&pool).await?

```

### WAL Corruption Handling

If database initialization fails due to a corrupted **Write-Ahead-Log (WAL)**, the implementation removes orphaned `.wal` and `.shm` files and retries the connection. This recovery mechanism at lines 78‑110 handles abrupt shutdown scenarios and guarantees a recoverable start.

## Manual Legacy Database Import

Beyond automatic detection, the `import_legacy_database` helper function allows importing user-provided `.db` files from arbitrary locations. It copies the specified file to the legacy path and invokes `new_from_app_handle`, reusing the standard detection and migration flow (lines 32‑58):

```rust
#[tauri::command]
async fn import_legacy(app_handle: tauri::AppHandle, path: String) -> Result<(), String> {
    DatabaseManager::import_legacy_database(&app_handle, &path)
        .await
        .map_err(|e| e.to_string())?;
    Ok(())
}

```

## Tauri Integration and Initialization

The database manager integrates with the Tauri application lifecycle through the `setup` hook. The `init_db` command initializes the connection and triggers the legacy detection workflow automatically when the application starts (lines 44‑78):

```rust
use crate::database::manager::DatabaseManager;

#[tauri::command]
async fn init_db(app_handle: tauri::AppHandle) -> Result<(), String> {
    DatabaseManager::new_from_app_handle(&app_handle)
        .await
        .map_err(|e| e.to_string())?;
    Ok(())
}

```

## Summary

- Meetily checks for `meeting_minutes.db` (legacy FastAPI) and `meeting_minutes.sqlite` (current Tauri) in the application data directory during startup.
- If only the legacy file exists, the system copies it to the new filename before opening the connection.
- **SQLx migrations** located in `frontend/src-tauri/migrations/` upgrade the schema automatically after copying.
- **WAL corruption** is handled by deleting orphaned `.wal` and `.shm` files and retrying connections.
- The `import_legacy_database` function supports manual import of external legacy databases via Tauri commands.

## Frequently Asked Questions

### What distinguishes the legacy database from the current Meetily database?

The legacy database uses the filename `meeting_minutes.db` from the original FastAPI backend, while the current Tauri implementation uses `meeting_minutes.sqlite`. Both are SQLite files, but the current format includes updated schema versions managed through SQLx migrations in the `frontend/src-tauri/migrations/` directory.

### Where does Meetily store migration scripts?

Migration scripts are stored in `frontend/src-tauri/migrations/` and compiled into the binary at build time. The `sqlx::migrate!("./migrations")` macro loads these scripts and applies them via `.run(&pool).await?` when the database connection initializes, ensuring the legacy schema upgrades to the current version.

### How does the application recover from database corruption?

If opening the database fails due to WAL corruption, the code in [`manager.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/manager.rs) (lines 78‑110) detects the specific error condition, removes the orphaned `.wal` and `.shm` journal files, and retries the connection. This ensures the application can start reliably even after an abrupt system shutdown or power loss.

### Can I import a legacy database from a custom file path?

Yes. The `import_legacy_database` function accepts a file path parameter and copies the specified file to the expected legacy location before processing it through the standard migration workflow. This functionality is exposed as a Tauri command, allowing the frontend to trigger imports of user-provided `.db` files.