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:
asepharyana
2026-07-21 12:54:23 +07:00
co-authored by Claude Opus 4.8
parent 80c96eaa42
commit 776fa828d8
205 changed files with 4241 additions and 14569 deletions
+155
View File
@@ -0,0 +1,155 @@
//! Event bus implementation.
use async_trait::async_trait;
use std::{any::TypeId, collections::HashMap, sync::Arc};
use tokio::sync::{broadcast, RwLock};
use tracing::{debug, info};
/// Trait for events that can be published.
pub trait Event: Clone + Send + Sync + 'static {
/// Event name for logging/debugging.
const NAME: &'static str;
}
/// Trait for event handlers.
#[async_trait]
pub trait EventHandler<E: Event>: Send + Sync {
async fn handle(&self, event: E);
}
/// The event bus for publishing and subscribing to events.
pub struct EventBus {
channels: RwLock<HashMap<TypeId, Box<dyn std::any::Any + Send + Sync>>>,
}
impl EventBus {
/// Create a new event bus.
pub fn new() -> Self {
Self {
channels: RwLock::new(HashMap::new()),
}
}
/// Publish an event to all subscribers.
pub async fn publish<E: Event>(&self, event: E) {
let type_id = TypeId::of::<E>();
let channels = self.channels.read().await;
if let Some(sender) = channels.get(&type_id) {
if let Some(tx) = sender.downcast_ref::<broadcast::Sender<E>>() {
let _ = tx.send(event);
debug!("Published event: {}", E::NAME);
}
}
}
/// Subscribe to events of a specific type.
/// Returns a receiver that can be used to receive events.
pub async fn subscribe<E: Event>(&self) -> broadcast::Receiver<E> {
let type_id = TypeId::of::<E>();
// Check if channel exists
{
let channels = self.channels.read().await;
if let Some(sender) = channels.get(&type_id) {
if let Some(tx) = sender.downcast_ref::<broadcast::Sender<E>>() {
return tx.subscribe();
}
}
}
// Create new channel
let (tx, rx) = broadcast::channel::<E>(100);
{
let mut channels = self.channels.write().await;
channels.insert(type_id, Box::new(tx));
}
// Re-get the receiver from the stored sender
let channels = self.channels.read().await;
if let Some(sender) = channels.get(&type_id) {
if let Some(tx) = sender.downcast_ref::<broadcast::Sender<E>>() {
return tx.subscribe();
}
}
rx
}
/// Register a handler for a specific event type.
/// The handler will be called whenever an event of that type is published.
pub async fn on<E: Event, H: EventHandler<E> + 'static>(&self, handler: H) {
let mut rx = self.subscribe::<E>().await;
let handler = Arc::new(handler);
tokio::spawn(async move {
loop {
match rx.recv().await {
Ok(event) => {
handler.handle(event).await;
}
Err(broadcast::error::RecvError::Closed) => break,
Err(broadcast::error::RecvError::Lagged(n)) => {
tracing::warn!("Event handler lagged by {} events", n);
}
}
}
});
info!("Registered handler for event: {}", E::NAME);
}
}
impl Default for EventBus {
fn default() -> Self {
Self::new()
}
}
// Common events
/// User registered event.
#[derive(Clone, Debug)]
pub struct UserRegistered {
pub user_id: String,
pub email: String,
pub name: String,
}
impl Event for UserRegistered {
const NAME: &'static str = "user.registered";
}
/// User logged in event.
#[derive(Clone, Debug)]
pub struct UserLoggedIn {
pub user_id: String,
pub ip_address: Option<String>,
}
impl Event for UserLoggedIn {
const NAME: &'static str = "user.logged_in";
}
/// Order created event.
#[derive(Clone, Debug)]
pub struct OrderCreated {
pub order_id: String,
pub user_id: String,
pub total: f64,
}
impl Event for OrderCreated {
const NAME: &'static str = "order.created";
}
/// Image repaired event.
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct ImageRepaired {
pub original_url: String,
pub cdn_url: String,
}
impl Event for ImageRepaired {
const NAME: &'static str = "image.repaired";
}