init: scaffold from k-template with postgres + worker

This commit is contained in:
2026-05-31 03:08:38 +02:00
commit f9cb142c3b
70 changed files with 5269 additions and 0 deletions

View 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(),
}
}
}

View File

@@ -0,0 +1,58 @@
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};
use adapters_postgres::{connect, run_migrations, PostgresUserRepository};
use adapters_storage::{ObjectStorageAdapter, StorageConfig, build_store};
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?;
let user_repo = Arc::new(PostgresUserRepository::new(pool));
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));
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(
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))
}

View File

View File

@@ -0,0 +1,28 @@
use std::net::SocketAddr;
use tracing::info;
mod config;
mod factory;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::from_default_env()
.add_directive("bootstrap=info".parse()?)
.add_directive("tower_http=debug".parse()?),
)
.init();
let config = config::Config::from_env();
let app = factory::build_app(&config).await?;
let addr: SocketAddr = format!("{}:{}", config.host, config.port).parse()?;
let listener = tokio::net::TcpListener::bind(addr).await?;
info!("🚀 Server running at http://{addr}");
info!("📖 Scalar docs at http://{addr}/scalar");
axum::serve(listener, app).await?;
Ok(())
}