diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b423adf..f6dee4da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,4 +83,4 @@ jobs: push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - build-args: FEATURES=sqlite,sqlite-federation + build-args: FEATURES=federation diff --git a/Cargo.lock b/Cargo.lock index 839c63fc..2aeadf3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1154,6 +1154,7 @@ dependencies = [ "sqlite-social", "sqlx", "tokio", + "tracing", "uuid", ] diff --git a/Dockerfile b/Dockerfile index d136920e..886a2f86 100644 --- a/Dockerfile +++ b/Dockerfile @@ -74,11 +74,10 @@ COPY crates ./crates # All sqlx queries use the runtime API (no query! macros), so no database # or .sqlx cache is needed at compile time. # -# To build with PostgreSQL backend instead: -# --build-arg FEATURES=postgres,postgres-federation -# To add NATS support (EVENT_BUS_BACKEND=nats): -# --build-arg FEATURES=sqlite,sqlite-federation,nats -ARG FEATURES=sqlite,sqlite-federation +# Features: federation (ActivityPub), nats (EVENT_BUS_BACKEND=nats). +# Build without federation: --build-arg FEATURES= +# To add NATS support: --build-arg FEATURES=federation,nats +ARG FEATURES=federation RUN cargo build --release -p server -p worker --no-default-features --features "${FEATURES}" # ----- runtime ----- diff --git a/crates/composition/Cargo.toml b/crates/composition/Cargo.toml index de444330..0599e0de 100644 --- a/crates/composition/Cargo.toml +++ b/crates/composition/Cargo.toml @@ -5,10 +5,8 @@ edition.workspace = true [features] default = [] -sqlite = ["dep:sqlite", "dep:sqlite-event-queue", "dep:sqlite-search", "dep:sqlite-social", "infra-wiring/sqlite"] nats = ["dep:nats", "infra-wiring/nats"] -federation = ["application/federation"] -sqlite-federation = ["sqlite", "dep:sqlite-federation", "dep:activitypub", "federation"] +federation = ["application/federation", "dep:sqlite-federation", "dep:activitypub"] [dependencies] domain = { workspace = true } @@ -21,11 +19,12 @@ object-storage = { workspace = true } jellyfin = { workspace = true } plex = { workspace = true } anyhow = { workspace = true } +tracing = { workspace = true } -sqlite = { workspace = true, optional = true } -sqlite-event-queue = { workspace = true, optional = true } -sqlite-search = { workspace = true, optional = true } -sqlite-social = { workspace = true, optional = true } +sqlite = { workspace = true } +sqlite-event-queue = { workspace = true } +sqlite-search = { workspace = true } +sqlite-social = { workspace = true } nats = { workspace = true, optional = true } activitypub = { workspace = true, optional = true } sqlite-federation = { workspace = true, optional = true } diff --git a/crates/composition/src/factory.rs b/crates/composition/src/factory.rs index efe5fa16..34580381 100644 --- a/crates/composition/src/factory.rs +++ b/crates/composition/src/factory.rs @@ -123,9 +123,14 @@ pub fn build_profile_fields_repo( pool: &DbPool, ) -> anyhow::Result> { match pool { - #[cfg(feature = "sqlite")] DbPool::Sqlite(pool) => Ok(sqlite::create_profile_fields_repo(pool.clone())), - #[cfg(not(feature = "sqlite"))] - _ => anyhow::bail!("no profile fields repo for this backend"), + } +} + +pub fn warn_if_database_backend_set() { + if let Ok(v) = std::env::var("DATABASE_BACKEND") + && v != "sqlite" + { + tracing::warn!("DATABASE_BACKEND is ignored; SQLite is the only backend"); } } diff --git a/crates/composition/src/tests/build.rs b/crates/composition/src/tests/build.rs index 6a506570..e0746cf4 100644 --- a/crates/composition/src/tests/build.rs +++ b/crates/composition/src/tests/build.rs @@ -265,11 +265,7 @@ fn test_repositories() -> Repositories { } /// A lazily-connected in-memory sqlite pool — never actually opened in this test, -/// just a value to satisfy `DatabaseOutput::db_pool`. Requires the `sqlite` feature, -/// which `cargo test --workspace` enables transitively (presentation/worker both -/// default to it), matching how `DbPool::Sqlite` is only constructible under that -/// feature everywhere else in the workspace. -#[cfg(feature = "sqlite")] +/// just a value to satisfy `DatabaseOutput::db_pool`. fn test_db_pool() -> infra_wiring::DbPool { let pool = sqlx::sqlite::SqlitePoolOptions::new() .connect_lazy("sqlite::memory:") @@ -277,14 +273,6 @@ fn test_db_pool() -> infra_wiring::DbPool { infra_wiring::DbPool::Sqlite(pool) } -#[cfg(not(feature = "sqlite"))] -fn test_db_pool() -> infra_wiring::DbPool { - unreachable!( - "worker_deps_wiring needs the sqlite feature; cargo test --workspace enables it \ - transitively via presentation/worker's default features" - ) -} - fn test_database_output() -> DatabaseOutput { let movies = InMemoryMovieRepository::new(); let watch_events = InMemoryWatchEventRepository::new(); diff --git a/crates/infra-wiring/Cargo.toml b/crates/infra-wiring/Cargo.toml index fcacb81f..228f8686 100644 --- a/crates/infra-wiring/Cargo.toml +++ b/crates/infra-wiring/Cargo.toml @@ -9,5 +9,4 @@ anyhow = { workspace = true } [features] default = [] -sqlite = [] nats = [] diff --git a/crates/infra-wiring/src/lib.rs b/crates/infra-wiring/src/lib.rs index bb9dc566..b4320c09 100644 --- a/crates/infra-wiring/src/lib.rs +++ b/crates/infra-wiring/src/lib.rs @@ -3,7 +3,6 @@ pub mod config; pub use config::{AppConfig, WrapUpConfig}; pub enum DbPool { - #[cfg(feature = "sqlite")] Sqlite(sqlx::SqlitePool), } diff --git a/crates/presentation/src/tests/extractors.rs b/crates/presentation/src/tests/extractors.rs index 58ae5c3a..d6fc267b 100644 --- a/crates/presentation/src/tests/extractors.rs +++ b/crates/presentation/src/tests/extractors.rs @@ -495,7 +495,6 @@ impl SearchCommand for Panic { panic!() } } -#[cfg(feature = "federation")] #[async_trait::async_trait] impl domain::ports::RemoteWatchlistRepository for Panic { async fn save(&self, _: domain::models::RemoteWatchlistEntry) -> Result<(), DomainError> { diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml index bc333407..ca4fe627 100644 --- a/crates/server/Cargo.toml +++ b/crates/server/Cargo.toml @@ -6,19 +6,18 @@ description = "Self-hosted movie diary with REST API and ActivityPub federation" license = "MIT" [features] -default = ["sqlite", "sqlite-federation"] -sqlite = ["dep:sqlite", "dep:sqlite-event-queue", "dep:sqlite-search", "infra-wiring/sqlite", "composition/sqlite"] -nats = ["dep:nats", "infra-wiring/nats", "composition/nats"] -# Meta-feature: true when any federation adapter is active — keeps all #[cfg(feature = "federation")] gates working -federation = ["application/federation", "presentation/federation"] -sqlite-federation = [ - "sqlite", - "dep:sqlite-federation", - "dep:sqlite-social", +default = ["federation"] +federation = [ "dep:activitypub", - "federation", - "composition/sqlite-federation", + "dep:sqlite-federation", + "application/federation", + "presentation/federation", + "composition/federation", ] +nats = ["dep:nats", "infra-wiring/nats", "composition/nats"] +# deprecated aliases kept so existing deploy commands keep working +sqlite = [] +sqlite-federation = ["federation"] [dependencies] axum = { workspace = true } @@ -44,15 +43,14 @@ sqlx = { workspace = true } infra-wiring = { workspace = true } async-trait = { workspace = true } -# Optional — database backends -sqlite = { workspace = true, optional = true } -sqlite-event-queue = { workspace = true, optional = true } -sqlite-search = { workspace = true, optional = true } +sqlite = { workspace = true } +sqlite-event-queue = { workspace = true } +sqlite-search = { workspace = true } +sqlite-social = { workspace = true } # Optional — federation -activitypub = { workspace = true, optional = true } +activitypub = { workspace = true, optional = true } sqlite-federation = { workspace = true, optional = true } -sqlite-social = { workspace = true, optional = true } [dev-dependencies] bytes = { workspace = true } diff --git a/crates/server/src/main.rs b/crates/server/src/main.rs index 8d14a952..72c47a4a 100644 --- a/crates/server/src/main.rs +++ b/crates/server/src/main.rs @@ -16,9 +16,6 @@ use rss::RssAdapter; use domain::ports::{DiaryExporter, DocumentParser, EventPublisher}; use infra_wiring::EventBusBackend; -#[cfg(not(feature = "sqlite"))] -compile_error!("The sqlite feature must be enabled. Use --features sqlite"); - #[tokio::main] async fn main() -> anyhow::Result<()> { dotenvy::dotenv().ok(); @@ -48,7 +45,7 @@ async fn wire_dependencies() -> anyhow::Result<(AppState, axum::Router)> { let app_config = AppConfig::from_env(); let instance = domain::value_objects::InstanceIdentity::new(app_config.base_url.clone()); let database_url = std::env::var("DATABASE_URL").context("DATABASE_URL must be set")?; - warn_if_database_backend_set(); + composition::factory::warn_if_database_backend_set(); let (auth_service, password_hasher) = composition::factory::build_auth_adapters()?; let metadata_client = composition::factory::build_metadata_client()?; @@ -76,7 +73,6 @@ async fn wire_dependencies() -> anyhow::Result<(AppState, axum::Router)> { block_query_arc, ) = { let fed_repos = match &db_pool { - #[cfg(feature = "sqlite-federation")] composition::DbPool::Sqlite(pool) => { sqlite_federation::wire(pool.clone(), instance.clone()) } @@ -214,7 +210,6 @@ async fn wire_dependencies() -> anyhow::Result<(AppState, axum::Router)> { #[cfg(feature = "federation")] federated_profile: Some({ match &db_pool { - #[cfg(feature = "sqlite-federation")] composition::DbPool::Sqlite(pool) => { sqlite_social::create_federated_profile_query(pool.clone(), instance.clone()) } @@ -272,7 +267,6 @@ async fn create_event_publisher( EventBusBackend::Db => { tracing::info!("event bus: DB queue"); Ok(match db_pool { - #[cfg(feature = "sqlite")] composition::DbPool::Sqlite(pool) => { sqlite_event_queue::SqliteEventQueue::create_publisher(pool.clone()).await? } @@ -288,14 +282,6 @@ async fn create_event_publisher( } } -fn warn_if_database_backend_set() { - if let Ok(v) = std::env::var("DATABASE_BACKEND") - && v != "sqlite" - { - tracing::warn!("DATABASE_BACKEND is ignored; SQLite is the only backend"); - } -} - fn init_tracing() { tracing_subscriber::registry() .with(log_filter("server=debug,tower_http=debug")) diff --git a/crates/server/tests/api_test.rs b/crates/server/tests/api_test.rs index a8698c22..0ff2a4f9 100644 --- a/crates/server/tests/api_test.rs +++ b/crates/server/tests/api_test.rs @@ -362,9 +362,7 @@ impl SearchCommand for PanicSearchCommand { } } -#[cfg(feature = "federation")] struct PanicRemoteWatchlist; -#[cfg(feature = "federation")] #[async_trait::async_trait] impl domain::ports::RemoteWatchlistRepository for PanicRemoteWatchlist { async fn save(&self, _: domain::models::RemoteWatchlistEntry) -> Result<(), DomainError> { diff --git a/crates/worker/Cargo.toml b/crates/worker/Cargo.toml index d81add48..de04f955 100644 --- a/crates/worker/Cargo.toml +++ b/crates/worker/Cargo.toml @@ -4,17 +4,17 @@ version = "0.1.0" edition.workspace = true [features] -default = ["sqlite", "sqlite-federation"] -sqlite = ["dep:sqlite", "dep:sqlite-event-queue", "dep:sqlite-search", "infra-wiring/sqlite", "composition/sqlite"] -nats = ["dep:nats", "infra-wiring/nats", "composition/nats"] -federation = ["application/federation"] -sqlite-federation = [ - "sqlite", - "dep:sqlite-federation", +default = ["federation"] +federation = [ "dep:activitypub", - "federation", - "composition/sqlite-federation", + "dep:sqlite-federation", + "application/federation", + "composition/federation", ] +nats = ["dep:nats", "infra-wiring/nats", "composition/nats"] +# deprecated aliases kept so existing deploy commands keep working +sqlite = [] +sqlite-federation = ["federation"] [dependencies] domain = { workspace = true } @@ -39,11 +39,10 @@ sqlx = { workspace = true } infra-wiring = { workspace = true } async-trait = { workspace = true } -# Optional — database backends -sqlite = { workspace = true, optional = true } -sqlite-event-queue = { workspace = true, optional = true } -sqlite-search = { workspace = true, optional = true } +sqlite = { workspace = true } +sqlite-event-queue = { workspace = true } +sqlite-search = { workspace = true } # Optional — federation -activitypub = { workspace = true, optional = true } +activitypub = { workspace = true, optional = true } sqlite-federation = { workspace = true, optional = true } diff --git a/crates/worker/src/event_bus.rs b/crates/worker/src/event_bus.rs index 24bfd543..556e302d 100644 --- a/crates/worker/src/event_bus.rs +++ b/crates/worker/src/event_bus.rs @@ -13,7 +13,6 @@ pub async fn create( EventBusBackend::Db => { tracing::info!("event bus: DB queue"); match db_pool { - #[cfg(feature = "sqlite")] DbPool::Sqlite(pool) => { Ok(sqlite_event_queue::SqliteEventQueue::create_channel(pool.clone()).await?) } diff --git a/crates/worker/src/main.rs b/crates/worker/src/main.rs index f2505713..b20cea59 100644 --- a/crates/worker/src/main.rs +++ b/crates/worker/src/main.rs @@ -13,16 +13,13 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use domain::ports::{EventHandler, MovieEnrichmentClient, PeriodicJob, PersonEnrichmentClient}; -#[cfg(not(feature = "sqlite"))] -compile_error!("The sqlite feature must be enabled. Use --features sqlite"); - #[tokio::main] async fn main() -> anyhow::Result<()> { dotenvy::dotenv().ok(); init_tracing(); let database_url = std::env::var("DATABASE_URL").context("DATABASE_URL must be set")?; - warn_if_database_backend_set(); + composition::factory::warn_if_database_backend_set(); let app_config = AppConfig::from_env(); let instance = domain::value_objects::InstanceIdentity::new(app_config.base_url.clone()); @@ -82,7 +79,6 @@ async fn main() -> anyhow::Result<()> { // Wire federation repos early to get remote_watchlist_repo for AppContext. #[cfg(feature = "federation")] let fed_repos = match &db.db_pool { - #[cfg(feature = "sqlite-federation")] composition::DbPool::Sqlite(pool) => { sqlite_federation::wire(pool.clone(), instance.clone()) } @@ -301,14 +297,6 @@ async fn main() -> anyhow::Result<()> { Ok(()) } -fn warn_if_database_backend_set() { - if let Ok(v) = std::env::var("DATABASE_BACKEND") - && v != "sqlite" - { - tracing::warn!("DATABASE_BACKEND is ignored; SQLite is the only backend"); - } -} - fn init_tracing() { tracing_subscriber::registry() .with(log_filter("worker=info,application=info,k_ap=info")) diff --git a/deploy.sh b/deploy.sh index 5e6f8ee5..8801669d 100755 --- a/deploy.sh +++ b/deploy.sh @@ -3,7 +3,7 @@ set -euo pipefail REGISTRY="registry.gabrielkaszewski.dev" REPO="movies-diary" -DEFAULT_FEATURES="sqlite,sqlite-federation,nats" +DEFAULT_FEATURES="federation,nats" features="${FEATURES:-$DEFAULT_FEATURES}" tag="latest" diff --git a/docker-compose.yml b/docker-compose.yml index 1c8a4994..c67f2596 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: # build: # context: . # args: - # FEATURES: sqlite,sqlite-federation + # FEATURES: federation ports: - "3000:3000" env_file: .env @@ -18,7 +18,7 @@ services: # build: # context: . # args: - # FEATURES: sqlite,sqlite-federation + # FEATURES: federation entrypoint: ["./worker"] env_file: .env volumes: