# How Meetily Structures Meetings, Transcripts, and Summaries with SQLite: Database Architecture Explained

> Discover how Meetily structures meetings, transcripts, and summaries in SQLite. Learn about type-safe Rust models, DatabaseManager for migrations, WAL recovery, and atomic transactions.

- Repository: [Zackriya Solutions/meetily](https://github.com/Zackriya-Solutions/meetily)
- Tags: architecture
- Published: 2026-07-26

---

**Meetily stores meetings, transcripts, and AI-generated summaries in a local SQLite database using type-safe Rust models and a centralized DatabaseManager that handles migrations, WAL recovery, and atomic transactions without external services.**

Meetily is an open-source AI meeting assistant that prioritizes data privacy through a local-first architecture. Understanding how Meetily's database module structures meetings, transcripts, and summaries with SQLite reveals a lightweight yet robust persistence layer built on Rust's sqlx crate. The implementation resides entirely within the Tauri backend at `frontend/src-tauri/src/database`, providing relational data management through five normalized tables and corresponding Rust structs.

## SQLite Schema Design for Meeting Data

The database file `meeting_minutes.sqlite` lives in the user's Tauri application data directory. The initial migration script at [`frontend/src-tauri/migrations/20250916100000_initial_schema.sql`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/migrations/20250916100000_initial_schema.sql) (lines 1‑73) establishes a relational schema optimized for meeting workflows:

| Table | Purpose | Key Relationships |
|-------|---------|-------------------|
| **meetings** | Represents a single meeting session | `id` (Primary Key) |
| **transcripts** | Stores raw transcription text and optional AI outputs | `meeting_id` (Foreign Key → meetings.id) |
| **summary_processes** | Tracks async AI summarization jobs | `meeting_id` (Primary Key + Foreign Key) |
| **transcript_chunks** | Holds chunked transcript data for processing | `meeting_id` (Foreign Key) |
| **settings** | Persists user-level LLM and STT configuration | Standalone configuration table |

The **transcripts** table includes optional columns for `summary`, `action_items`, and `key_points`, allowing the schema to accommodate both raw transcription data and processed AI outputs. The **summary_processes** table stores processing status and JSON-encoded results, enabling the UI to track summarization progress in real-time.

## Rust Data Models with sqlx

Type-safe database access is implemented in [`frontend/src-tauri/src/database/models.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/database/models.rs), where structs derive `sqlx::FromRow` to map directly to SQLite rows:

* **MeetingModel** (lines 5‑12) mirrors the `meetings` table with fields for `id`, `title`, `created_at`, `updated_at`, and optional `folder_path`.
* **Transcript** (lines 24‑38) maps to the `transcripts` table and adds audio-synchronization fields including `audio_start_time`, `audio_end_time`, and `duration`.
* **SummaryProcess** (lines 40‑55) reflects the `summary_processes` table, storing `status`, timestamps, and a JSON `result` field for AI outputs.
* **TranscriptChunk** (lines 58‑67) represents chunked data storage with `transcript_text`, `model`, `chunk_size`, and `overlap` parameters.

These models enable compile-time checked SQL queries through sqlx macros, eliminating runtime schema mismatches between Rust code and the SQLite database.

## Database Manager Implementation

All database interactions flow through the **DatabaseManager** defined in [`frontend/src-tauri/src/database/manager.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/database/manager.rs). This singleton manages connection pooling, error recovery, and schema migrations.

### Connection Initialization and Migrations

The manager initializes the SQLite pool via `DatabaseManager::new_from_app_handle(&app_handle)`, which automatically resolves the Tauri data directory. If the database file does not exist, it creates the file and executes the embedded migration bundle (`sqlx::migrate!("./migrations")`) to establish the schema (lines 19‑36).

### WAL Mode Recovery

To prevent corruption issues common in SQLite write-ahead logging mode, the manager implements startup guards that detect invalid WAL/SHM files. It removes corrupted auxiliary files and retries the connection until successful (lines 72‑108).

### Legacy Migration Support

For users upgrading from Meetily's archived Python backend, the manager detects legacy `.db` files, copies them to the new Tauri data directory location, and opens them using standard SQLite handling (lines 54‑63).

### Transactional Operations

The `with_transaction` method provides atomic execution for multi-statement operations, ensuring data consistency when inserting related records across the **meetings**, **transcripts**, and **summary_processes** tables (lines 64‑81).

## Practical Data Flow Examples

The following Rust examples demonstrate how Meetily's database module structures meetings, transcripts, and summaries in practice.

### Creating a Meeting Record

```rust
// Initialize the database manager from Tauri app handle
let db = DatabaseManager::new_from_app_handle(&app_handle).await?;

// Construct a new meeting model
let meeting = MeetingModel {
    id: uuid::Uuid::new_v4().to_string(),
    title: "Team Stand-up".into(),
    created_at: DateTimeUtc::from(Utc::now().naive_utc()),
    updated_at: DateTimeUtc::from(Utc::now().naive_utc()),
    folder_path: None,
};

// Insert into the meetings table
sqlx::query_as!(
    MeetingModel,
    "INSERT INTO meetings (id, title, created_at, updated_at) VALUES (?1, ?2, ?3, ?4)",
    meeting.id,
    meeting.title,
    meeting.created_at.0,
    meeting.updated_at.0
)
.execute(db.pool())
.await?;

```

### Storing Transcript Data

```rust
// Prepare transcript with audio synchronization metadata
let transcript = Transcript {
    id: uuid::Uuid::new_v4().to_string(),
    meeting_id: meeting.id.clone(),
    transcript: raw_text,
    timestamp: Utc::now().to_rfc3339(),
    summary: None,
    action_items: None,
    key_points: None,
    audio_start_time: Some(start_sec),
    audio_end_time: Some(end_sec),
    duration: Some(end_sec - start_sec),
};

// Persist to the transcripts table linked by meeting_id
sqlx::query_as!(
    Transcript,
    "INSERT INTO transcripts (id, meeting_id, transcript, timestamp, audio_start_time, audio_end_time, duration) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
    transcript.id,
    transcript.meeting_id,
    transcript.transcript,
    transcript.timestamp,
    transcript.audio_start_time,
    transcript.audio_end_time,
    transcript.duration
)
.execute(db.pool())
.await?;

```

### Updating AI Summary Status

```rust
// Update the summary_processes table when LLM processing completes
sqlx::query!(
    "UPDATE summary_processes SET status = ?1, result = ?2, updated_at = ?3 WHERE meeting_id = ?4",
    "completed",
    json_result,
    Utc::now().to_rfc3339(),
    meeting.id
)
.execute(db.pool())
.await?;

```

## Summary

- Meetily uses a **local SQLite database** (`meeting_minutes.sqlite`) stored in the Tauri data directory to ensure complete data privacy without external database dependencies.
- The **schema** separates concerns into five normalized tables: `meetings`, `transcripts`, `summary_processes`, `transcript_chunks`, and settings tables.
- **Rust models** in [`models.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/models.rs) provide type-safe mapping through `sqlx::FromRow`, supporting fields for audio synchronization and JSON-stored AI results.
- The **DatabaseManager** in [`manager.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/manager.rs) handles connection pooling, automatic migrations, WAL file recovery, and legacy database upgrades from the Python backend.
- All operations use **parameterized queries** through sqlx macros, preventing SQL injection while enabling compile-time schema validation.

## Frequently Asked Questions

### Where does Meetily store the SQLite database file?

The database is stored as `meeting_minutes.sqlite` in the user's Tauri application data directory, resolved automatically by `app_handle.path_resolver().app_data_dir()`. This ensures the data remains local to the device and is not synced to external servers.

### How does Meetily handle database schema migrations?

Meetily uses sqlx's embedded migration system defined in [`frontend/src-tauri/migrations/20250916100000_initial_schema.sql`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/migrations/20250916100000_initial_schema.sql). On startup, the DatabaseManager runs `sqlx::migrate!("./migrations")` to apply any pending schema changes automatically, versioning the database alongside the application code.

### What happens if the SQLite WAL file becomes corrupted?

The DatabaseManager includes startup guards that detect corrupted WAL (Write-Ahead Log) and SHM files. It removes these corrupted auxiliary files and re-establishes the database connection, allowing SQLite to regenerate fresh transaction logs without data loss in the main database file.

### Can Meetily import data from the previous Python backend version?

Yes, the DatabaseManager detects legacy `.db` files from the archived Python backend during initialization. It copies the existing data to the new Tauri data directory location (`meeting_minutes.sqlite`) before opening the connection, facilitating seamless migration for existing users.