Infra refactor
This commit is contained in:
@@ -10,13 +10,9 @@ use axum_login::AuthManagerLayerBuilder;
|
||||
use tower_http::cors::CorsLayer;
|
||||
use tower_http::trace::TraceLayer;
|
||||
use tower_sessions::{Expiry, SessionManagerLayer};
|
||||
use tower_sessions_sqlx_store::SqliteStore;
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
use notes_infra::{
|
||||
DatabaseConfig, SqliteNoteRepository, SqliteTagRepository, SqliteUserRepository, create_pool,
|
||||
run_migrations,
|
||||
};
|
||||
use notes_infra::{DatabaseConfig, run_migrations};
|
||||
|
||||
mod auth;
|
||||
mod config;
|
||||
@@ -46,29 +42,66 @@ async fn main() -> anyhow::Result<()> {
|
||||
// Setup database
|
||||
tracing::info!("Connecting to database: {}", config.database_url);
|
||||
let db_config = DatabaseConfig::new(&config.database_url);
|
||||
let pool = create_pool(&db_config).await?;
|
||||
|
||||
use notes_infra::factory::{
|
||||
build_database_pool, build_note_repository, build_session_store, build_tag_repository,
|
||||
build_user_repository,
|
||||
};
|
||||
let pool = build_database_pool(&db_config)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!(e))?;
|
||||
|
||||
// Run migrations
|
||||
tracing::info!("Running database migrations...");
|
||||
run_migrations(&pool).await?;
|
||||
// The factory/infra layer abstracts the database type
|
||||
if let Err(e) = run_migrations(&pool).await {
|
||||
tracing::warn!(
|
||||
"Migration error (might be expected if not implemented for this DB): {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
// Create a default user for development (optional now that we have registration)
|
||||
create_dev_user(&pool).await?;
|
||||
// Create a default user for development
|
||||
create_dev_user(&pool).await.ok();
|
||||
|
||||
// Create repositories
|
||||
let note_repo = Arc::new(SqliteNoteRepository::new(pool.clone()));
|
||||
let tag_repo = Arc::new(SqliteTagRepository::new(pool.clone()));
|
||||
let user_repo = Arc::new(SqliteUserRepository::new(pool.clone()));
|
||||
// Create repositories via factory
|
||||
let note_repo = build_note_repository(&pool)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!(e))?;
|
||||
let tag_repo = build_tag_repository(&pool)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!(e))?;
|
||||
let user_repo = build_user_repository(&pool)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!(e))?;
|
||||
|
||||
// Create services
|
||||
use notes_domain::{NoteService, TagService, UserService};
|
||||
let note_service = Arc::new(NoteService::new(note_repo.clone(), tag_repo.clone()));
|
||||
let tag_service = Arc::new(TagService::new(tag_repo.clone()));
|
||||
let user_service = Arc::new(UserService::new(user_repo.clone()));
|
||||
|
||||
// Create application state
|
||||
let state = AppState::new(note_repo, tag_repo, user_repo.clone());
|
||||
let state = AppState::new(
|
||||
note_repo,
|
||||
tag_repo,
|
||||
user_repo.clone(),
|
||||
note_service,
|
||||
tag_service,
|
||||
user_service,
|
||||
);
|
||||
|
||||
// Auth backend
|
||||
let backend = AuthBackend::new(user_repo);
|
||||
|
||||
// Session layer
|
||||
let session_store = SqliteStore::new(pool.clone());
|
||||
session_store.migrate().await?;
|
||||
// Use the factory to build the session store, agnostic of the underlying DB
|
||||
let session_store = build_session_store(&pool)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!(e))?;
|
||||
session_store
|
||||
.migrate()
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!(e))?;
|
||||
|
||||
let session_layer = SessionManagerLayer::new(session_store)
|
||||
.with_secure(false) // Set to true in production with HTTPS
|
||||
@@ -129,30 +162,29 @@ async fn main() -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a development user for testing
|
||||
/// In production, users will be created via OIDC authentication
|
||||
async fn create_dev_user(pool: &sqlx::SqlitePool) -> anyhow::Result<()> {
|
||||
use notes_domain::{User, UserRepository};
|
||||
use notes_infra::SqliteUserRepository;
|
||||
async fn create_dev_user(pool: ¬es_infra::db::DatabasePool) -> anyhow::Result<()> {
|
||||
use notes_domain::User;
|
||||
use notes_infra::factory::build_user_repository;
|
||||
use password_auth::generate_hash;
|
||||
use uuid::Uuid;
|
||||
|
||||
let user_repo = SqliteUserRepository::new(pool.clone());
|
||||
let user_repo = build_user_repository(pool)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!(e))?;
|
||||
|
||||
// Check if dev user exists
|
||||
let dev_user_id = Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap();
|
||||
if user_repo.find_by_id(dev_user_id).await?.is_none() {
|
||||
// Create dev user with fixed ID and password 'password'
|
||||
let hash = generate_hash("password");
|
||||
let user = User::with_id(
|
||||
dev_user_id,
|
||||
"dev|local",
|
||||
"dev@localhost",
|
||||
"dev@localhost.com",
|
||||
Some(hash),
|
||||
chrono::Utc::now(),
|
||||
);
|
||||
user_repo.save(&user).await?;
|
||||
tracing::info!("Created development user: dev@localhost / password");
|
||||
tracing::info!("Created development user: dev@localhost.com / password");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -10,9 +10,7 @@ use uuid::Uuid;
|
||||
use validator::Validate;
|
||||
|
||||
use axum_login::AuthUser;
|
||||
use notes_domain::{
|
||||
CreateNoteRequest as DomainCreateNote, NoteService, UpdateNoteRequest as DomainUpdateNote,
|
||||
};
|
||||
use notes_domain::{CreateNoteRequest as DomainCreateNote, UpdateNoteRequest as DomainUpdateNote};
|
||||
|
||||
use crate::auth::AuthBackend;
|
||||
use crate::dto::{CreateNoteRequest, ListNotesQuery, NoteResponse, SearchQuery, UpdateNoteRequest};
|
||||
@@ -48,8 +46,7 @@ pub async fn list_notes(
|
||||
}
|
||||
}
|
||||
|
||||
let service = NoteService::new(state.note_repo, state.tag_repo);
|
||||
let notes = service.list_notes(user_id, filter).await?;
|
||||
let notes = state.note_service.list_notes(user_id, filter).await?;
|
||||
let response: Vec<NoteResponse> = notes.into_iter().map(NoteResponse::from).collect();
|
||||
|
||||
Ok(Json(response))
|
||||
@@ -74,8 +71,6 @@ pub async fn create_note(
|
||||
.validate()
|
||||
.map_err(|e| ApiError::validation(e.to_string()))?;
|
||||
|
||||
let service = NoteService::new(state.note_repo, state.tag_repo);
|
||||
|
||||
let domain_req = DomainCreateNote {
|
||||
user_id,
|
||||
title: payload.title,
|
||||
@@ -85,7 +80,7 @@ pub async fn create_note(
|
||||
is_pinned: payload.is_pinned,
|
||||
};
|
||||
|
||||
let note = service.create_note(domain_req).await?;
|
||||
let note = state.note_service.create_note(domain_req).await?;
|
||||
|
||||
Ok((StatusCode::CREATED, Json(NoteResponse::from(note))))
|
||||
}
|
||||
@@ -104,9 +99,7 @@ pub async fn get_note(
|
||||
)))?;
|
||||
let user_id = user.id();
|
||||
|
||||
let service = NoteService::new(state.note_repo, state.tag_repo);
|
||||
|
||||
let note = service.get_note(id, user_id).await?;
|
||||
let note = state.note_service.get_note(id, user_id).await?;
|
||||
|
||||
Ok(Json(NoteResponse::from(note)))
|
||||
}
|
||||
@@ -131,8 +124,6 @@ pub async fn update_note(
|
||||
.validate()
|
||||
.map_err(|e| ApiError::validation(e.to_string()))?;
|
||||
|
||||
let service = NoteService::new(state.note_repo, state.tag_repo);
|
||||
|
||||
let domain_req = DomainUpdateNote {
|
||||
id,
|
||||
user_id,
|
||||
@@ -144,7 +135,7 @@ pub async fn update_note(
|
||||
tags: payload.tags,
|
||||
};
|
||||
|
||||
let note = service.update_note(domain_req).await?;
|
||||
let note = state.note_service.update_note(domain_req).await?;
|
||||
|
||||
Ok(Json(NoteResponse::from(note)))
|
||||
}
|
||||
@@ -163,9 +154,7 @@ pub async fn delete_note(
|
||||
)))?;
|
||||
let user_id = user.id();
|
||||
|
||||
let service = NoteService::new(state.note_repo, state.tag_repo);
|
||||
|
||||
service.delete_note(id, user_id).await?;
|
||||
state.note_service.delete_note(id, user_id).await?;
|
||||
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
@@ -184,9 +173,7 @@ pub async fn search_notes(
|
||||
)))?;
|
||||
let user_id = user.id();
|
||||
|
||||
let service = NoteService::new(state.note_repo, state.tag_repo);
|
||||
|
||||
let notes = service.search_notes(user_id, &query.q).await?;
|
||||
let notes = state.note_service.search_notes(user_id, &query.q).await?;
|
||||
let response: Vec<NoteResponse> = notes.into_iter().map(NoteResponse::from).collect();
|
||||
|
||||
Ok(Json(response))
|
||||
@@ -206,9 +193,7 @@ pub async fn list_note_versions(
|
||||
)))?;
|
||||
let user_id = user.id();
|
||||
|
||||
let service = NoteService::new(state.note_repo, state.tag_repo);
|
||||
|
||||
let versions = service.list_note_versions(id, user_id).await?;
|
||||
let versions = state.note_service.list_note_versions(id, user_id).await?;
|
||||
let response: Vec<crate::dto::NoteVersionResponse> = versions
|
||||
.into_iter()
|
||||
.map(crate::dto::NoteVersionResponse::from)
|
||||
|
||||
@@ -9,8 +9,6 @@ use axum_login::{AuthSession, AuthUser};
|
||||
use uuid::Uuid;
|
||||
use validator::Validate;
|
||||
|
||||
use notes_domain::TagService;
|
||||
|
||||
use crate::auth::AuthBackend;
|
||||
use crate::dto::{CreateTagRequest, RenameTagRequest, TagResponse};
|
||||
use crate::error::{ApiError, ApiResult};
|
||||
@@ -29,9 +27,7 @@ pub async fn list_tags(
|
||||
)))?;
|
||||
let user_id = user.id();
|
||||
|
||||
let service = TagService::new(state.tag_repo);
|
||||
|
||||
let tags = service.list_tags(user_id).await?;
|
||||
let tags = state.tag_service.list_tags(user_id).await?;
|
||||
let response: Vec<TagResponse> = tags.into_iter().map(TagResponse::from).collect();
|
||||
|
||||
Ok(Json(response))
|
||||
@@ -55,9 +51,7 @@ pub async fn create_tag(
|
||||
.validate()
|
||||
.map_err(|e| ApiError::validation(e.to_string()))?;
|
||||
|
||||
let service = TagService::new(state.tag_repo);
|
||||
|
||||
let tag = service.create_tag(user_id, &payload.name).await?;
|
||||
let tag = state.tag_service.create_tag(user_id, &payload.name).await?;
|
||||
|
||||
Ok((StatusCode::CREATED, Json(TagResponse::from(tag))))
|
||||
}
|
||||
@@ -81,9 +75,10 @@ pub async fn rename_tag(
|
||||
.validate()
|
||||
.map_err(|e| ApiError::validation(e.to_string()))?;
|
||||
|
||||
let service = TagService::new(state.tag_repo);
|
||||
|
||||
let tag = service.rename_tag(id, user_id, &payload.name).await?;
|
||||
let tag = state
|
||||
.tag_service
|
||||
.rename_tag(id, user_id, &payload.name)
|
||||
.await?;
|
||||
|
||||
Ok(Json(TagResponse::from(tag)))
|
||||
}
|
||||
@@ -102,9 +97,7 @@ pub async fn delete_tag(
|
||||
)))?;
|
||||
let user_id = user.id();
|
||||
|
||||
let service = TagService::new(state.tag_repo);
|
||||
|
||||
service.delete_tag(id, user_id).await?;
|
||||
state.tag_service.delete_tag(id, user_id).await?;
|
||||
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
//! Application state for dependency injection
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use notes_domain::{NoteRepository, TagRepository, UserRepository};
|
||||
use notes_domain::{
|
||||
NoteRepository, NoteService, TagRepository, TagService, UserRepository, UserService,
|
||||
};
|
||||
|
||||
/// Application state holding all dependencies
|
||||
#[derive(Clone)]
|
||||
@@ -10,6 +10,9 @@ pub struct AppState {
|
||||
pub note_repo: Arc<dyn NoteRepository>,
|
||||
pub tag_repo: Arc<dyn TagRepository>,
|
||||
pub user_repo: Arc<dyn UserRepository>,
|
||||
pub note_service: Arc<NoteService>,
|
||||
pub tag_service: Arc<TagService>,
|
||||
pub user_service: Arc<UserService>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
@@ -17,11 +20,17 @@ impl AppState {
|
||||
note_repo: Arc<dyn NoteRepository>,
|
||||
tag_repo: Arc<dyn TagRepository>,
|
||||
user_repo: Arc<dyn UserRepository>,
|
||||
note_service: Arc<NoteService>,
|
||||
tag_service: Arc<TagService>,
|
||||
user_service: Arc<UserService>,
|
||||
) -> Self {
|
||||
Self {
|
||||
note_repo,
|
||||
tag_repo,
|
||||
user_repo,
|
||||
note_service,
|
||||
tag_service,
|
||||
user_service,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user