feat: Implement conditional compilation for SQLite and Postgres database backends using Cargo features.
This commit is contained in:
@@ -6,7 +6,7 @@ default-run = "notes-api"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
notes-domain = { path = "../notes-domain" }
|
notes-domain = { path = "../notes-domain" }
|
||||||
notes-infra = { path = "../notes-infra" }
|
notes-infra = { path = "../notes-infra", features = ["sqlite"] }
|
||||||
|
|
||||||
# Web framework
|
# Web framework
|
||||||
axum = { version = "0.8.8", features = ["macros"] }
|
axum = { version = "0.8.8", features = ["macros"] }
|
||||||
|
|||||||
@@ -3,14 +3,19 @@ name = "notes-infra"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["sqlite"]
|
||||||
|
sqlite = ["sqlx/sqlite", "tower-sessions-sqlx-store/sqlite"]
|
||||||
|
postgres = ["sqlx/postgres", "tower-sessions-sqlx-store/postgres"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
notes-domain = { path = "../notes-domain" }
|
notes-domain = { path = "../notes-domain" }
|
||||||
async-trait = "0.1.89"
|
async-trait = "0.1.89"
|
||||||
chrono = { version = "0.4.42", features = ["serde"] }
|
chrono = { version = "0.4.42", features = ["serde"] }
|
||||||
sqlx = { version = "0.8.6", features = ["sqlite", "runtime-tokio", "chrono", "migrate", "postgres"] }
|
sqlx = { version = "0.8.6", features = ["runtime-tokio", "chrono", "migrate"] }
|
||||||
thiserror = "2.0.17"
|
thiserror = "2.0.17"
|
||||||
tokio = { version = "1.48.0", features = ["full"] }
|
tokio = { version = "1.48.0", features = ["full"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
uuid = { version = "1.19.0", features = ["v4", "serde"] }
|
uuid = { version = "1.19.0", features = ["v4", "serde"] }
|
||||||
tower-sessions = "0.14.0"
|
tower-sessions = "0.14.0"
|
||||||
tower-sessions-sqlx-store = { version = "0.15.0", features = ["sqlite", "postgres"] }
|
tower-sessions-sqlx-store = { version = "0.15.0", default-features = false }
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
//! Database connection pool management
|
//! Database connection pool management
|
||||||
|
|
||||||
|
use sqlx::Pool;
|
||||||
|
#[cfg(feature = "postgres")]
|
||||||
|
use sqlx::Postgres;
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
use sqlx::Sqlite;
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
use sqlx::sqlite::{SqliteConnectOptions, SqlitePool, SqlitePoolOptions};
|
use sqlx::sqlite::{SqliteConnectOptions, SqlitePool, SqlitePoolOptions};
|
||||||
use sqlx::{Pool, Postgres, Sqlite};
|
#[cfg(feature = "sqlite")]
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -45,11 +51,14 @@ impl DatabaseConfig {
|
|||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum DatabasePool {
|
pub enum DatabasePool {
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
Sqlite(Pool<Sqlite>),
|
Sqlite(Pool<Sqlite>),
|
||||||
|
#[cfg(feature = "postgres")]
|
||||||
Postgres(Pool<Postgres>),
|
Postgres(Pool<Postgres>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a database connection pool
|
/// Create a database connection pool
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
pub async fn create_pool(config: &DatabaseConfig) -> Result<SqlitePool, sqlx::Error> {
|
pub async fn create_pool(config: &DatabaseConfig) -> Result<SqlitePool, sqlx::Error> {
|
||||||
let options = SqliteConnectOptions::from_str(&config.url)?
|
let options = SqliteConnectOptions::from_str(&config.url)?
|
||||||
.create_if_missing(true)
|
.create_if_missing(true)
|
||||||
@@ -70,9 +79,11 @@ pub async fn create_pool(config: &DatabaseConfig) -> Result<SqlitePool, sqlx::Er
|
|||||||
/// Run database migrations
|
/// Run database migrations
|
||||||
pub async fn run_migrations(pool: &DatabasePool) -> Result<(), sqlx::Error> {
|
pub async fn run_migrations(pool: &DatabasePool) -> Result<(), sqlx::Error> {
|
||||||
match pool {
|
match pool {
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
DatabasePool::Sqlite(pool) => {
|
DatabasePool::Sqlite(pool) => {
|
||||||
sqlx::migrate!("../migrations").run(pool).await?;
|
sqlx::migrate!("../migrations").run(pool).await?;
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "postgres")]
|
||||||
DatabasePool::Postgres(_pool) => {
|
DatabasePool::Postgres(_pool) => {
|
||||||
// Placeholder for Postgres migrations
|
// Placeholder for Postgres migrations
|
||||||
// sqlx::migrate!("../migrations/postgres").run(_pool).await?;
|
// sqlx::migrate!("../migrations/postgres").run(_pool).await?;
|
||||||
@@ -81,6 +92,12 @@ pub async fn run_migrations(pool: &DatabasePool) -> Result<(), sqlx::Error> {
|
|||||||
"Postgres migrations not yet implemented".into(),
|
"Postgres migrations not yet implemented".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
#[allow(unreachable_patterns)]
|
||||||
|
_ => {
|
||||||
|
return Err(sqlx::Error::Configuration(
|
||||||
|
"No database feature enabled".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::info!("Database migrations completed successfully");
|
tracing::info!("Database migrations completed successfully");
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{DatabaseConfig, db::DatabasePool};
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
use crate::{SqliteNoteRepository, SqliteTagRepository, SqliteUserRepository};
|
||||||
use notes_domain::{NoteRepository, TagRepository, UserRepository};
|
use notes_domain::{NoteRepository, TagRepository, UserRepository};
|
||||||
|
|
||||||
pub use crate::db::DatabasePool;
|
|
||||||
use crate::{DatabaseConfig, SqliteNoteRepository, SqliteTagRepository, SqliteUserRepository};
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum FactoryError {
|
pub enum FactoryError {
|
||||||
#[error("Database error: {0}")]
|
#[error("Database error: {0}")]
|
||||||
@@ -17,17 +17,31 @@ pub type FactoryResult<T> = Result<T, FactoryError>;
|
|||||||
|
|
||||||
pub async fn build_database_pool(db_config: &DatabaseConfig) -> FactoryResult<DatabasePool> {
|
pub async fn build_database_pool(db_config: &DatabaseConfig) -> FactoryResult<DatabasePool> {
|
||||||
if db_config.url.starts_with("sqlite:") {
|
if db_config.url.starts_with("sqlite:") {
|
||||||
let pool = sqlx::sqlite::SqlitePoolOptions::new()
|
#[cfg(feature = "sqlite")]
|
||||||
.max_connections(5)
|
{
|
||||||
.connect(&db_config.url)
|
let pool = sqlx::sqlite::SqlitePoolOptions::new()
|
||||||
.await?;
|
.max_connections(5)
|
||||||
Ok(DatabasePool::Sqlite(pool))
|
.connect(&db_config.url)
|
||||||
|
.await?;
|
||||||
|
Ok(DatabasePool::Sqlite(pool))
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "sqlite"))]
|
||||||
|
Err(FactoryError::NotImplemented(
|
||||||
|
"SQLite feature not enabled".to_string(),
|
||||||
|
))
|
||||||
} else if db_config.url.starts_with("postgres:") {
|
} else if db_config.url.starts_with("postgres:") {
|
||||||
let pool = sqlx::postgres::PgPoolOptions::new()
|
#[cfg(feature = "postgres")]
|
||||||
.max_connections(5)
|
{
|
||||||
.connect(&db_config.url)
|
let pool = sqlx::postgres::PgPoolOptions::new()
|
||||||
.await?;
|
.max_connections(5)
|
||||||
Ok(DatabasePool::Postgres(pool))
|
.connect(&db_config.url)
|
||||||
|
.await?;
|
||||||
|
Ok(DatabasePool::Postgres(pool))
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "postgres"))]
|
||||||
|
Err(FactoryError::NotImplemented(
|
||||||
|
"Postgres feature not enabled".to_string(),
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
Err(FactoryError::NotImplemented(format!(
|
Err(FactoryError::NotImplemented(format!(
|
||||||
"Unsupported database URL scheme in: {}",
|
"Unsupported database URL scheme in: {}",
|
||||||
@@ -38,28 +52,46 @@ pub async fn build_database_pool(db_config: &DatabaseConfig) -> FactoryResult<Da
|
|||||||
|
|
||||||
pub async fn build_note_repository(pool: &DatabasePool) -> FactoryResult<Arc<dyn NoteRepository>> {
|
pub async fn build_note_repository(pool: &DatabasePool) -> FactoryResult<Arc<dyn NoteRepository>> {
|
||||||
match pool {
|
match pool {
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
DatabasePool::Sqlite(pool) => Ok(Arc::new(SqliteNoteRepository::new(pool.clone()))),
|
DatabasePool::Sqlite(pool) => Ok(Arc::new(SqliteNoteRepository::new(pool.clone()))),
|
||||||
|
#[cfg(feature = "postgres")]
|
||||||
DatabasePool::Postgres(_) => Err(FactoryError::NotImplemented(
|
DatabasePool::Postgres(_) => Err(FactoryError::NotImplemented(
|
||||||
"Postgres NoteRepository".to_string(),
|
"Postgres NoteRepository".to_string(),
|
||||||
)),
|
)),
|
||||||
|
#[allow(unreachable_patterns)]
|
||||||
|
_ => Err(FactoryError::NotImplemented(
|
||||||
|
"No database feature enabled".to_string(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn build_tag_repository(pool: &DatabasePool) -> FactoryResult<Arc<dyn TagRepository>> {
|
pub async fn build_tag_repository(pool: &DatabasePool) -> FactoryResult<Arc<dyn TagRepository>> {
|
||||||
match pool {
|
match pool {
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
DatabasePool::Sqlite(pool) => Ok(Arc::new(SqliteTagRepository::new(pool.clone()))),
|
DatabasePool::Sqlite(pool) => Ok(Arc::new(SqliteTagRepository::new(pool.clone()))),
|
||||||
|
#[cfg(feature = "postgres")]
|
||||||
DatabasePool::Postgres(_) => Err(FactoryError::NotImplemented(
|
DatabasePool::Postgres(_) => Err(FactoryError::NotImplemented(
|
||||||
"Postgres TagRepository".to_string(),
|
"Postgres TagRepository".to_string(),
|
||||||
)),
|
)),
|
||||||
|
#[allow(unreachable_patterns)]
|
||||||
|
_ => Err(FactoryError::NotImplemented(
|
||||||
|
"No database feature enabled".to_string(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn build_user_repository(pool: &DatabasePool) -> FactoryResult<Arc<dyn UserRepository>> {
|
pub async fn build_user_repository(pool: &DatabasePool) -> FactoryResult<Arc<dyn UserRepository>> {
|
||||||
match pool {
|
match pool {
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
DatabasePool::Sqlite(pool) => Ok(Arc::new(SqliteUserRepository::new(pool.clone()))),
|
DatabasePool::Sqlite(pool) => Ok(Arc::new(SqliteUserRepository::new(pool.clone()))),
|
||||||
|
#[cfg(feature = "postgres")]
|
||||||
DatabasePool::Postgres(_) => Err(FactoryError::NotImplemented(
|
DatabasePool::Postgres(_) => Err(FactoryError::NotImplemented(
|
||||||
"Postgres UserRepository".to_string(),
|
"Postgres UserRepository".to_string(),
|
||||||
)),
|
)),
|
||||||
|
#[allow(unreachable_patterns)]
|
||||||
|
_ => Err(FactoryError::NotImplemented(
|
||||||
|
"No database feature enabled".to_string(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,13 +99,19 @@ pub async fn build_session_store(
|
|||||||
pool: &DatabasePool,
|
pool: &DatabasePool,
|
||||||
) -> FactoryResult<crate::session_store::InfraSessionStore> {
|
) -> FactoryResult<crate::session_store::InfraSessionStore> {
|
||||||
match pool {
|
match pool {
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
DatabasePool::Sqlite(pool) => {
|
DatabasePool::Sqlite(pool) => {
|
||||||
let store = tower_sessions_sqlx_store::SqliteStore::new(pool.clone());
|
let store = tower_sessions_sqlx_store::SqliteStore::new(pool.clone());
|
||||||
Ok(crate::session_store::InfraSessionStore::Sqlite(store))
|
Ok(crate::session_store::InfraSessionStore::Sqlite(store))
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "postgres")]
|
||||||
DatabasePool::Postgres(pool) => {
|
DatabasePool::Postgres(pool) => {
|
||||||
let store = tower_sessions_sqlx_store::PostgresStore::new(pool.clone());
|
let store = tower_sessions_sqlx_store::PostgresStore::new(pool.clone());
|
||||||
Ok(crate::session_store::InfraSessionStore::Postgres(store))
|
Ok(crate::session_store::InfraSessionStore::Postgres(store))
|
||||||
}
|
}
|
||||||
|
#[allow(unreachable_patterns)]
|
||||||
|
_ => Err(FactoryError::NotImplemented(
|
||||||
|
"No database feature enabled".to_string(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,13 +16,21 @@
|
|||||||
|
|
||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod factory;
|
pub mod factory;
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
pub mod note_repository;
|
pub mod note_repository;
|
||||||
pub mod session_store;
|
pub mod session_store;
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
pub mod tag_repository;
|
pub mod tag_repository;
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
pub mod user_repository;
|
pub mod user_repository;
|
||||||
|
|
||||||
// Re-export for convenience
|
// Re-export for convenience
|
||||||
pub use db::{DatabaseConfig, create_pool, run_migrations};
|
#[cfg(feature = "sqlite")]
|
||||||
|
pub use db::create_pool;
|
||||||
|
pub use db::{DatabaseConfig, run_migrations};
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
pub use note_repository::SqliteNoteRepository;
|
pub use note_repository::SqliteNoteRepository;
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
pub use tag_repository::SqliteTagRepository;
|
pub use tag_repository::SqliteTagRepository;
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
pub use user_repository::SqliteUserRepository;
|
pub use user_repository::SqliteUserRepository;
|
||||||
|
|||||||
@@ -4,11 +4,16 @@ use tower_sessions::{
|
|||||||
SessionStore,
|
SessionStore,
|
||||||
session::{Id, Record},
|
session::{Id, Record},
|
||||||
};
|
};
|
||||||
use tower_sessions_sqlx_store::{PostgresStore, SqliteStore};
|
#[cfg(feature = "postgres")]
|
||||||
|
use tower_sessions_sqlx_store::PostgresStore;
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
|
use tower_sessions_sqlx_store::SqliteStore;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum InfraSessionStore {
|
pub enum InfraSessionStore {
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
Sqlite(SqliteStore),
|
Sqlite(SqliteStore),
|
||||||
|
#[cfg(feature = "postgres")]
|
||||||
Postgres(PostgresStore),
|
Postgres(PostgresStore),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,22 +21,40 @@ pub enum InfraSessionStore {
|
|||||||
impl SessionStore for InfraSessionStore {
|
impl SessionStore for InfraSessionStore {
|
||||||
async fn save(&self, session_record: &Record) -> tower_sessions::session_store::Result<()> {
|
async fn save(&self, session_record: &Record) -> tower_sessions::session_store::Result<()> {
|
||||||
match self {
|
match self {
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
Self::Sqlite(store) => store.save(session_record).await,
|
Self::Sqlite(store) => store.save(session_record).await,
|
||||||
|
#[cfg(feature = "postgres")]
|
||||||
Self::Postgres(store) => store.save(session_record).await,
|
Self::Postgres(store) => store.save(session_record).await,
|
||||||
|
#[allow(unreachable_patterns)]
|
||||||
|
_ => Err(tower_sessions::session_store::Error::Backend(
|
||||||
|
"No backend enabled".to_string(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn load(&self, session_id: &Id) -> tower_sessions::session_store::Result<Option<Record>> {
|
async fn load(&self, session_id: &Id) -> tower_sessions::session_store::Result<Option<Record>> {
|
||||||
match self {
|
match self {
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
Self::Sqlite(store) => store.load(session_id).await,
|
Self::Sqlite(store) => store.load(session_id).await,
|
||||||
|
#[cfg(feature = "postgres")]
|
||||||
Self::Postgres(store) => store.load(session_id).await,
|
Self::Postgres(store) => store.load(session_id).await,
|
||||||
|
#[allow(unreachable_patterns)]
|
||||||
|
_ => Err(tower_sessions::session_store::Error::Backend(
|
||||||
|
"No backend enabled".to_string(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete(&self, session_id: &Id) -> tower_sessions::session_store::Result<()> {
|
async fn delete(&self, session_id: &Id) -> tower_sessions::session_store::Result<()> {
|
||||||
match self {
|
match self {
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
Self::Sqlite(store) => store.delete(session_id).await,
|
Self::Sqlite(store) => store.delete(session_id).await,
|
||||||
|
#[cfg(feature = "postgres")]
|
||||||
Self::Postgres(store) => store.delete(session_id).await,
|
Self::Postgres(store) => store.delete(session_id).await,
|
||||||
|
#[allow(unreachable_patterns)]
|
||||||
|
_ => Err(tower_sessions::session_store::Error::Backend(
|
||||||
|
"No backend enabled".to_string(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,8 +62,12 @@ impl SessionStore for InfraSessionStore {
|
|||||||
impl InfraSessionStore {
|
impl InfraSessionStore {
|
||||||
pub async fn migrate(&self) -> Result<(), sqlx::Error> {
|
pub async fn migrate(&self) -> Result<(), sqlx::Error> {
|
||||||
match self {
|
match self {
|
||||||
|
#[cfg(feature = "sqlite")]
|
||||||
Self::Sqlite(store) => store.migrate().await,
|
Self::Sqlite(store) => store.migrate().await,
|
||||||
|
#[cfg(feature = "postgres")]
|
||||||
Self::Postgres(store) => store.migrate().await,
|
Self::Postgres(store) => store.migrate().await,
|
||||||
|
#[allow(unreachable_patterns)]
|
||||||
|
_ => Err(sqlx::Error::Configuration("No backend enabled".into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user