feat(storage): add generic object storage adapter with CQRS traits, key validation, StorageConfig, and cargo-generate integration
This commit is contained in:
@@ -10,7 +10,8 @@ path = "src/main.rs"
|
||||
[dependencies]
|
||||
domain = { workspace = true }
|
||||
application = { workspace = true }
|
||||
adapters-auth = { workspace = true }
|
||||
adapters-auth = { workspace = true }
|
||||
adapters-storage = { workspace = true }
|
||||
presentation = { workspace = true }
|
||||
adapters-sqlite = { path = "../adapters/sqlite" }
|
||||
tokio = { workspace = true }
|
||||
|
||||
@@ -11,6 +11,15 @@ path = "src/main.rs"
|
||||
domain = { workspace = true }
|
||||
application = { workspace = true }
|
||||
adapters-auth = { workspace = true }
|
||||
{% if storage and storage_s3 and storage_gcs %}
|
||||
adapters-storage = { workspace = true, features = ["s3", "gcs"] }
|
||||
{% elsif storage and storage_s3 %}
|
||||
adapters-storage = { workspace = true, features = ["s3"] }
|
||||
{% elsif storage and storage_gcs %}
|
||||
adapters-storage = { workspace = true, features = ["gcs"] }
|
||||
{% elsif storage %}
|
||||
adapters-storage = { workspace = true }
|
||||
{% endif %}
|
||||
presentation = { workspace = true }
|
||||
{% if database == "sqlite" %}
|
||||
adapters-sqlite = { path = "../adapters/sqlite" }
|
||||
|
||||
28
crates/bootstrap/src/config.rs.liquid
Normal file
28
crates/bootstrap/src/config.rs.liquid
Normal file
@@ -0,0 +1,28 @@
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Config {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub database_url: String,
|
||||
pub jwt_secret: String,
|
||||
pub cors_allowed_origins: Vec<String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn from_env() -> Self {
|
||||
dotenvy::dotenv().ok();
|
||||
Self {
|
||||
host: std::env::var("HOST").unwrap_or_else(|_| "0.0.0.0".to_string()),
|
||||
port: std::env::var("PORT")
|
||||
.ok()
|
||||
.and_then(|p| p.parse().ok())
|
||||
.unwrap_or(3000),
|
||||
database_url: std::env::var("DATABASE_URL").expect("DATABASE_URL must be set"),
|
||||
jwt_secret: std::env::var("JWT_SECRET").expect("JWT_SECRET must be set"),
|
||||
cors_allowed_origins: std::env::var("CORS_ALLOWED_ORIGINS")
|
||||
.unwrap_or_else(|_| "http://localhost:3000".to_string())
|
||||
.split(',')
|
||||
.map(|s| s.trim().to_string())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
// If you chose postgres at cargo generate time, replace adapters_sqlite with
|
||||
// adapters_postgres throughout this file (connect, run_migrations, PostgresUserRepository).
|
||||
use std::sync::Arc;
|
||||
use anyhow::Result;
|
||||
use axum::Router;
|
||||
@@ -8,6 +6,7 @@ use tower_http::{cors::{Any, CorsLayer}, trace::TraceLayer};
|
||||
|
||||
use adapters_auth::{BcryptPasswordHasher, JwtTokenIssuer};
|
||||
use adapters_sqlite::{connect, run_migrations, SqliteUserRepository};
|
||||
use adapters_storage::{ObjectStorageAdapter, StorageConfig, build_store};
|
||||
use application::use_cases::{GetProfile, LoginUser, RegisterUser};
|
||||
use presentation::{routes::app_router, state::AppState};
|
||||
|
||||
@@ -25,7 +24,13 @@ pub async fn build_app(config: &Config) -> Result<Router> {
|
||||
let login_uc = Arc::new(LoginUser::new(user_repo.clone(), hasher, issuer.clone()));
|
||||
let get_profile_uc = Arc::new(GetProfile::new(user_repo));
|
||||
|
||||
let state = AppState::new(register_uc, login_uc, get_profile_uc, issuer);
|
||||
let storage_cfg = StorageConfig::from_env()?;
|
||||
let store = build_store(&storage_cfg)?;
|
||||
// To inject storage into a use case, clone it into the constructor:
|
||||
// let my_uc = Arc::new(MyUseCase::new(repo, storage.clone()));
|
||||
let storage = Arc::new(ObjectStorageAdapter::new(store, &storage_cfg.prefix)?);
|
||||
|
||||
let state = AppState::new(register_uc, login_uc, get_profile_uc, issuer, storage);
|
||||
|
||||
let cors = CorsLayer::new()
|
||||
.allow_origin(
|
||||
|
||||
62
crates/bootstrap/src/factory.rs.liquid
Normal file
62
crates/bootstrap/src/factory.rs.liquid
Normal file
@@ -0,0 +1,62 @@
|
||||
use std::sync::Arc;
|
||||
use anyhow::Result;
|
||||
use axum::Router;
|
||||
use axum::http::HeaderValue;
|
||||
use tower_http::{cors::{Any, CorsLayer}, trace::TraceLayer};
|
||||
|
||||
use adapters_auth::{BcryptPasswordHasher, JwtTokenIssuer};
|
||||
{% if database == "sqlite" %}
|
||||
use adapters_sqlite::{connect, run_migrations, SqliteUserRepository};
|
||||
{% endif %}
|
||||
{% if database == "postgres" %}
|
||||
use adapters_postgres::{connect, run_migrations, PostgresUserRepository};
|
||||
{% endif %}
|
||||
{% if storage %}
|
||||
use adapters_storage::{ObjectStorageAdapter, StorageConfig, build_store};
|
||||
{% endif %}
|
||||
use application::use_cases::{GetProfile, LoginUser, RegisterUser};
|
||||
use presentation::{routes::app_router, state::AppState};
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
pub async fn build_app(config: &Config) -> Result<Router> {
|
||||
let pool = connect(&config.database_url).await?;
|
||||
run_migrations(&pool).await?;
|
||||
|
||||
{% if database == "sqlite" %}
|
||||
let user_repo = Arc::new(SqliteUserRepository::new(pool));
|
||||
{% endif %}
|
||||
{% if database == "postgres" %}
|
||||
let user_repo = Arc::new(PostgresUserRepository::new(pool));
|
||||
{% endif %}
|
||||
let hasher = Arc::new(BcryptPasswordHasher);
|
||||
let issuer = Arc::new(JwtTokenIssuer::new(&config.jwt_secret));
|
||||
|
||||
let register_uc = Arc::new(RegisterUser::new(user_repo.clone(), hasher.clone()));
|
||||
let login_uc = Arc::new(LoginUser::new(user_repo.clone(), hasher, issuer.clone()));
|
||||
let get_profile_uc = Arc::new(GetProfile::new(user_repo));
|
||||
|
||||
{% if storage %}
|
||||
let storage_cfg = StorageConfig::from_env()?;
|
||||
let store = build_store(&storage_cfg)?;
|
||||
// To inject storage into a use case, clone it into the constructor:
|
||||
// let my_uc = Arc::new(MyUseCase::new(repo, storage.clone()));
|
||||
let storage = Arc::new(ObjectStorageAdapter::new(store, &storage_cfg.prefix)?);
|
||||
{% endif %}
|
||||
|
||||
let state = AppState::new(register_uc, login_uc, get_profile_uc, issuer{% if storage %}, storage{% endif %});
|
||||
|
||||
let cors = CorsLayer::new()
|
||||
.allow_origin(
|
||||
config.cors_allowed_origins.iter()
|
||||
.filter_map(|o| o.parse::<HeaderValue>().ok())
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.allow_methods(Any)
|
||||
.allow_headers(Any);
|
||||
|
||||
Ok(app_router()
|
||||
.with_state(state)
|
||||
.layer(TraceLayer::new_for_http())
|
||||
.layer(cors))
|
||||
}
|
||||
Reference in New Issue
Block a user