How Meetily Detects and Handles Legacy Database Migrations

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.

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:

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:

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):

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):

#[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):

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 (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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →