pub mod calendars;
mod events;
mod resources;
mod short_ids;
mod todos;
#[cfg(test)]
mod migrations_tests;
#[cfg(test)]
mod tests_utils;
use std::error::Error;
use std::path::Path;
use std::sync::atomic::{AtomicU64, Ordering};
use sqlx::sqlite::{SqliteConnectOptions, SqlitePool, SqlitePoolOptions};
use crate::db::calendars::Calendars;
use crate::db::events::{EventRecord, Events};
use crate::db::resources::Resources;
use crate::db::short_ids::ShortIds;
use crate::db::todos::{TodoRecord, Todos};
use crate::{Event, Todo};
static IN_MEMORY_DB_COUNTER: AtomicU64 = AtomicU64::new(0);
#[derive(Debug, Clone)]
pub struct Db {
pool: SqlitePool,
pub events: Events,
pub todos: Todos,
pub short_ids: ShortIds,
pub resources: Resources,
pub calendars: Calendars,
}
impl Db {
pub async fn open(filename: Option<&Path>) -> Result<Self, Box<dyn Error>> {
let pool_opts = SqlitePoolOptions::new();
let (conn_opts, pool_opts) = if let Some(filename) = filename {
tracing::info!(dir = %filename.display(), "connecting to SQLite database");
let conn_opts = SqliteConnectOptions::new()
.filename(filename.to_str().ok_or("Invalid path encoding")?)
.create_if_missing(true);
(conn_opts, pool_opts)
} else {
tracing::info!("connecting to in-memory SQLite database");
let db_id = IN_MEMORY_DB_COUNTER.fetch_add(1, Ordering::SeqCst);
let db_name = format!("file:memdb_{db_id}:?mode=memory&cache=shared");
let conn_opts = SqliteConnectOptions::new()
.filename(db_name)
.in_memory(true);
(conn_opts, pool_opts.max_connections(1)) };
let pool = pool_opts
.connect_with(conn_opts)
.await
.map_err(|e| format!("Failed to connect to SQLite database: {e}"))?;
sqlx::migrate!("src/db/migrations") .run(&pool)
.await
.map_err(|e| format!("Failed to run migrations: {e}"))?;
tracing::debug!("ensuring tables in the database");
let events = Events::new(pool.clone());
let todos = Todos::new(pool.clone());
let short_ids = ShortIds::new(pool.clone());
let resources = Resources::new(pool.clone());
let calendars = Calendars::new(pool.clone());
Ok(Db {
pool,
events,
todos,
short_ids,
resources,
calendars,
})
}
pub async fn upsert_event(
&self,
uid: &str,
event: &impl Event,
calendar_id: &str,
) -> Result<(), Box<dyn Error>> {
let record = EventRecord::from_event(uid, event, calendar_id);
self.events
.upsert(record)
.await
.map_err(|e| format!("Failed to upsert event: {e}").into())
}
pub async fn upsert_todo(
&self,
uid: &str,
todo: &impl Todo,
calendar_id: &str,
) -> Result<(), Box<dyn Error>> {
let record = TodoRecord::from_todo(uid, todo, calendar_id);
self.todos
.upsert(&record)
.await
.map_err(|e| format!("Failed to upsert todo: {e}").into())
}
pub async fn close(self) -> Result<(), Box<dyn Error>> {
tracing::debug!("closing database connection");
self.pool.close().await;
Ok(())
}
}