feat: full clean architecture refactor — domain → application → infrastructure → presentation
- Restructure from Modular MVC to Clean Architecture with 4 strict layers - Domain: entities (anime/komik/proxy), Repository traits, typed errors - Application: use case classes with proper error propagation - Infrastructure: repository impls, parsers, Redis cache, HTTP/scraping, browser - Presentation: Axum handlers, DTOs, AppState, router, AppError+IntoResponse - Migrate all parsers (otakudesu, alqanime, komik) to native infra implementations - Replace once_cell::sync::Lazy/OnceCell with std::sync::LazyLock/OnceLock - Remove 150+ old files in modules/ and shared/ directories - Remove once_cell from Cargo.toml dependencies - Fix test/debug binaries to use new import paths - Zero new clippy warnings Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
80c96eaa42
commit
776fa828d8
+19
-10
@@ -1,3 +1,5 @@
|
||||
pub mod setup;
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use tokio::net::TcpListener;
|
||||
@@ -6,9 +8,9 @@ use axum::Router;
|
||||
use sea_orm::Database;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
use crate::shared::config::CONFIG;
|
||||
use crate::shared::database::get_redis_conn;
|
||||
use crate::shared::state::AppState;
|
||||
use crate::config::CONFIG;
|
||||
use crate::infrastructure::cache::redis_pool::get_redis_conn;
|
||||
use crate::presentation::state::AppState;
|
||||
|
||||
pub struct Application {
|
||||
pub port: u16,
|
||||
@@ -27,7 +29,7 @@ impl Application {
|
||||
tracing_subscriber::fmt().with_env_filter(env_filter).init();
|
||||
|
||||
// Initialize OpenTelemetry metrics
|
||||
crate::shared::observability::metrics::init_otel_metrics();
|
||||
crate::observability::metrics::init_otel_metrics();
|
||||
|
||||
tracing::info!("🚀 Scraper starting up...");
|
||||
tracing::info!(" Environment: {}", CONFIG.environment);
|
||||
@@ -46,8 +48,8 @@ impl Application {
|
||||
|
||||
// Browser Pool
|
||||
tracing::info!("Initializing browser pool...");
|
||||
let browser_config = crate::shared::browser::BrowserPoolConfig::default();
|
||||
match crate::shared::browser::pool::init_browser_pool(browser_config).await {
|
||||
let browser_config = crate::infrastructure::browser::BrowserPoolConfig::default();
|
||||
match crate::infrastructure::browser::pool::init_browser_pool(browser_config).await {
|
||||
Ok(_) => tracing::info!("✓ Browser pool initialized"),
|
||||
Err(e) => tracing::error!("⚠️ Failed to initialize browser pool: {}", e),
|
||||
}
|
||||
@@ -76,7 +78,7 @@ impl Application {
|
||||
tracing::info!("✓ SeaORM database connection established");
|
||||
|
||||
// Schema & Seeding
|
||||
if let Err(e) = crate::shared::database::setup::init(&db).await {
|
||||
if let Err(e) = crate::bootstrap::setup::init(&db).await {
|
||||
tracing::error!("Failed to init DB schema: {}", e);
|
||||
}
|
||||
|
||||
@@ -85,19 +87,26 @@ impl Application {
|
||||
let image_processing_semaphore = Arc::new(tokio::sync::Semaphore::new(
|
||||
CONFIG.image_processing_concurrency,
|
||||
));
|
||||
let event_bus = Arc::new(crate::shared::events::bus::EventBus::new());
|
||||
let event_bus = Arc::new(crate::events::bus::EventBus::new());
|
||||
|
||||
let redis_pool = crate::shared::database::redis_pool()
|
||||
let redis_pool = crate::infrastructure::cache::redis_pool::redis_pool()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to init Redis pool: {}", e))?;
|
||||
|
||||
use crate::infrastructure::repository::SeaOrmImageCacheRepository;
|
||||
let image_cache_repo = Arc::new(SeaOrmImageCacheRepository::new(
|
||||
db_arc.clone(),
|
||||
redis_pool.clone(),
|
||||
));
|
||||
|
||||
let app_state = Arc::new(AppState {
|
||||
redis_pool,
|
||||
db: db_arc.clone(),
|
||||
image_processing_semaphore,
|
||||
event_bus: event_bus.clone(),
|
||||
image_cache_repo,
|
||||
});
|
||||
|
||||
let app = crate::app::build_router(app_state, db_arc.clone()).await?;
|
||||
let app = crate::presentation::router::build_router(app_state.clone())?;
|
||||
|
||||
// Listener
|
||||
let port = CONFIG.server_port;
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
//! Database schema initialization.
|
||||
|
||||
use sea_orm::{ConnectionTrait, DatabaseConnection, Schema, Statement};
|
||||
use tracing::info;
|
||||
|
||||
use crate::infrastructure::persistence::entities::image_cache;
|
||||
|
||||
pub async fn init(db: &DatabaseConnection) -> Result<(), sea_orm::DbErr> {
|
||||
info!("🚀 Initializing database schema...");
|
||||
let backend = db.get_database_backend();
|
||||
let schema = Schema::new(backend);
|
||||
|
||||
let tables = vec![(
|
||||
"ImageCache",
|
||||
schema
|
||||
.create_table_from_entity(image_cache::Entity)
|
||||
.if_not_exists()
|
||||
.to_owned(),
|
||||
)];
|
||||
|
||||
for (name, stmt) in tables {
|
||||
match db.execute(backend.build(&stmt)).await {
|
||||
Ok(_) => info!(" ✓ Table '{}' checked/created", name),
|
||||
Err(e) => {
|
||||
tracing::error!(" [!] Failed to create table '{}': {}", name, e);
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let index_sql =
|
||||
"CREATE INDEX IF NOT EXISTS idx_image_cache_cdn_url ON \"ImageCache\" (cdn_url)";
|
||||
match db.execute(Statement::from_string(backend, index_sql)).await {
|
||||
Ok(_) => info!(" ✓ Index 'idx_image_cache_cdn_url' ensured"),
|
||||
Err(e) => {
|
||||
let err_str = e.to_string();
|
||||
if err_str.contains("already exists") || err_str.contains("duplicate") {
|
||||
info!(" ✓ Index 'idx_image_cache_cdn_url' already exists");
|
||||
} else {
|
||||
tracing::error!(" [!] Failed to create index on ImageCache: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!("✅ Database schema initialization complete.");
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user