mirror of
https://github.com/logos-messaging/libchat.git
synced 2026-03-27 22:53:07 +00:00
feat: run migrations from sql files
This commit is contained in:
parent
e099d5fd15
commit
f4c08bd048
@ -3,38 +3,10 @@
|
|||||||
use storage::{RusqliteError, SqliteDb, StorageConfig, StorageError, params};
|
use storage::{RusqliteError, SqliteDb, StorageConfig, StorageError, params};
|
||||||
use x25519_dalek::StaticSecret;
|
use x25519_dalek::StaticSecret;
|
||||||
|
|
||||||
|
use super::migrations;
|
||||||
use super::types::{ChatRecord, IdentityRecord};
|
use super::types::{ChatRecord, IdentityRecord};
|
||||||
use crate::identity::Identity;
|
use crate::identity::Identity;
|
||||||
|
|
||||||
/// Schema for chat storage tables.
|
|
||||||
/// Note: Ratchet state is stored by double_ratchets::RatchetStorage separately.
|
|
||||||
const CHAT_SCHEMA: &str = "
|
|
||||||
-- Identity table (single row)
|
|
||||||
CREATE TABLE IF NOT EXISTS identity (
|
|
||||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
secret_key BLOB NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Inbox ephemeral keys for handshakes
|
|
||||||
CREATE TABLE IF NOT EXISTS inbox_keys (
|
|
||||||
public_key_hex TEXT PRIMARY KEY,
|
|
||||||
secret_key BLOB NOT NULL,
|
|
||||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Chat metadata
|
|
||||||
CREATE TABLE IF NOT EXISTS chats (
|
|
||||||
chat_id TEXT PRIMARY KEY,
|
|
||||||
chat_type TEXT NOT NULL,
|
|
||||||
remote_public_key BLOB,
|
|
||||||
remote_address TEXT NOT NULL,
|
|
||||||
created_at INTEGER NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_chats_type ON chats(chat_type);
|
|
||||||
";
|
|
||||||
|
|
||||||
/// Chat-specific storage operations.
|
/// Chat-specific storage operations.
|
||||||
///
|
///
|
||||||
/// This struct wraps a SqliteDb and provides domain-specific
|
/// This struct wraps a SqliteDb and provides domain-specific
|
||||||
@ -49,12 +21,12 @@ impl ChatStorage {
|
|||||||
/// Creates a new ChatStorage with the given configuration.
|
/// Creates a new ChatStorage with the given configuration.
|
||||||
pub fn new(config: StorageConfig) -> Result<Self, StorageError> {
|
pub fn new(config: StorageConfig) -> Result<Self, StorageError> {
|
||||||
let db = SqliteDb::new(config)?;
|
let db = SqliteDb::new(config)?;
|
||||||
Self::run_migration(db)
|
Self::run_migrations(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new chat storage with the given database.
|
/// Applies all migrations and returns the storage instance.
|
||||||
fn run_migration(db: SqliteDb) -> Result<Self, StorageError> {
|
fn run_migrations(db: SqliteDb) -> Result<Self, StorageError> {
|
||||||
db.connection().execute_batch(CHAT_SCHEMA)?;
|
migrations::apply_migrations(db.connection())?;
|
||||||
Ok(Self { db })
|
Ok(Self { db })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
44
conversations/src/storage/migrations.rs
Normal file
44
conversations/src/storage/migrations.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
//! Database migrations module.
|
||||||
|
//!
|
||||||
|
//! SQL migrations are embedded at compile time and applied in order.
|
||||||
|
|
||||||
|
use storage::{Connection, StorageError};
|
||||||
|
|
||||||
|
/// Embeds and returns all migration SQL files in order.
|
||||||
|
pub fn get_migrations() -> Vec<(&'static str, &'static str)> {
|
||||||
|
vec![(
|
||||||
|
"001_initial_schema",
|
||||||
|
include_str!("migrations/001_initial_schema.sql"),
|
||||||
|
)]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies all migrations to the database.
|
||||||
|
/// Uses a simple version tracking table to avoid re-running migrations.
|
||||||
|
pub fn apply_migrations(conn: &Connection) -> Result<(), StorageError> {
|
||||||
|
// Create migrations tracking table if it doesn't exist
|
||||||
|
conn.execute_batch(
|
||||||
|
"CREATE TABLE IF NOT EXISTS _migrations (
|
||||||
|
name TEXT PRIMARY KEY,
|
||||||
|
applied_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||||
|
);",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
for (name, sql) in get_migrations() {
|
||||||
|
// Check if migration already applied
|
||||||
|
let already_applied: bool = conn.query_row(
|
||||||
|
"SELECT EXISTS(SELECT 1 FROM _migrations WHERE name = ?1)",
|
||||||
|
[name],
|
||||||
|
|row| row.get(0),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
if !already_applied {
|
||||||
|
// Apply migration
|
||||||
|
conn.execute_batch(sql)?;
|
||||||
|
|
||||||
|
// Record migration
|
||||||
|
conn.execute("INSERT INTO _migrations (name) VALUES (?1)", [name])?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
27
conversations/src/storage/migrations/001_initial_schema.sql
Normal file
27
conversations/src/storage/migrations/001_initial_schema.sql
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
-- Initial schema for chat storage
|
||||||
|
-- Migration: 001_initial_schema
|
||||||
|
|
||||||
|
-- Identity table (single row)
|
||||||
|
CREATE TABLE IF NOT EXISTS identity (
|
||||||
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
secret_key BLOB NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Inbox ephemeral keys for handshakes
|
||||||
|
CREATE TABLE IF NOT EXISTS inbox_keys (
|
||||||
|
public_key_hex TEXT PRIMARY KEY,
|
||||||
|
secret_key BLOB NOT NULL,
|
||||||
|
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Chat metadata
|
||||||
|
CREATE TABLE IF NOT EXISTS chats (
|
||||||
|
chat_id TEXT PRIMARY KEY,
|
||||||
|
chat_type TEXT NOT NULL,
|
||||||
|
remote_public_key BLOB,
|
||||||
|
remote_address TEXT NOT NULL,
|
||||||
|
created_at INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_chats_type ON chats(chat_type);
|
||||||
@ -7,6 +7,7 @@
|
|||||||
//! handles all storage operations automatically.
|
//! handles all storage operations automatically.
|
||||||
|
|
||||||
mod db;
|
mod db;
|
||||||
|
mod migrations;
|
||||||
pub(crate) mod types;
|
pub(crate) mod types;
|
||||||
|
|
||||||
pub(crate) use db::ChatStorage;
|
pub(crate) use db::ChatStorage;
|
||||||
|
|||||||
@ -12,4 +12,4 @@ pub use errors::StorageError;
|
|||||||
pub use sqlite::{SqliteDb, StorageConfig};
|
pub use sqlite::{SqliteDb, StorageConfig};
|
||||||
|
|
||||||
// Re-export rusqlite types that domain crates will need
|
// Re-export rusqlite types that domain crates will need
|
||||||
pub use rusqlite::{Error as RusqliteError, Transaction, params};
|
pub use rusqlite::{Connection, Error as RusqliteError, Transaction, params};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user