How Meetily Structures Meetings, Transcripts, and Summaries with SQLite: Database Architecture Explained
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 (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, where structs derive sqlx::FromRow to map directly to SQLite rows:
- MeetingModel (lines 5‑12) mirrors the
meetingstable with fields forid,title,created_at,updated_at, and optionalfolder_path. - Transcript (lines 24‑38) maps to the
transcriptstable and adds audio-synchronization fields includingaudio_start_time,audio_end_time, andduration. - SummaryProcess (lines 40‑55) reflects the
summary_processestable, storingstatus, timestamps, and a JSONresultfield for AI outputs. - TranscriptChunk (lines 58‑67) represents chunked data storage with
transcript_text,model,chunk_size, andoverlapparameters.
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. 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
// 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
// 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
// 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.rsprovide type-safe mapping throughsqlx::FromRow, supporting fields for audio synchronization and JSON-stored AI results. - The DatabaseManager in
manager.rshandles 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. 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →