refactor: reorganize project into monorepo with frontend scaffolding
Convert project from single backend to monorepo structure with separate frontend (Vue 3 + TypeScript + Vite) and backend directories. Updates all configuration files and build system to support both workspaces. Ref: T007 (specs/001-modbus-relay-control)
This commit is contained in:
78
backend/src/infrastructure/mod.rs
Normal file
78
backend/src/infrastructure/mod.rs
Normal file
@@ -0,0 +1,78 @@
|
||||
//! Infrastructure layer - External integrations and adapters
|
||||
//!
|
||||
//! This module implements the technical infrastructure required by the application,
|
||||
//! including external system integrations, persistence, and communication protocols.
|
||||
//! All infrastructure depends on domain/application layers through trait implementations.
|
||||
//!
|
||||
//! # Architecture Principles
|
||||
//!
|
||||
//! - **Implements domain traits**: Provides concrete implementations of domain interfaces
|
||||
//! - **Depends inward**: Depends on domain/application, never the reverse
|
||||
//! - **Substitutable**: Different implementations can be swapped without changing domain
|
||||
//! - **Framework-specific**: Contains framework and library dependencies (Modbus, SQLx, etc.)
|
||||
//!
|
||||
//! # Planned Submodules
|
||||
//!
|
||||
//! ## `modbus` - Modbus RTU over TCP Integration
|
||||
//!
|
||||
//! - `client`: ModbusRelayController implementation using tokio-modbus
|
||||
//! - `mock`: MockRelayController for testing without hardware
|
||||
//! - `config`: Modbus connection configuration
|
||||
//! - `connection`: Connection pool and health management
|
||||
//!
|
||||
//! Implements: [`domain::relay::controller::RelayController`](crate::domain)
|
||||
//!
|
||||
//! ## `persistence` - SQLite Database with SQLx
|
||||
//!
|
||||
//! - `sqlite_repository`: SqliteRelayLabelRepository implementation
|
||||
//! - `schema.sql`: Database schema with relay_labels table
|
||||
//! - `migrations`: Database migration scripts (if using sqlx-cli)
|
||||
//!
|
||||
//! Implements: [`domain::relay::repository::RelayLabelRepository`](crate::domain)
|
||||
//!
|
||||
//! # Technology Stack
|
||||
//!
|
||||
//! - **Modbus**: `tokio-modbus` 0.17.0 for async Modbus RTU over TCP
|
||||
//! - **Persistence**: `sqlx` 0.8 for compile-time verified SQLite queries
|
||||
//! - **Async Runtime**: `tokio` 1.48 (shared with main application)
|
||||
//! - **Testing**: `mockall` 0.13 for mock implementations
|
||||
//!
|
||||
//! # Implementation Pattern
|
||||
//!
|
||||
//! Each infrastructure adapter:
|
||||
//! 1. Implements domain-defined trait (e.g., `RelayController`)
|
||||
//! 2. Translates domain types to/from external formats
|
||||
//! 3. Handles connection management and retries
|
||||
//! 4. Provides error translation (external errors → domain errors)
|
||||
//!
|
||||
//! # Example: Modbus Controller
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! pub struct ModbusRelayController {
|
||||
//! client: Arc<Mutex<ModbusClient>>,
|
||||
//! config: ModbusConfig,
|
||||
//! }
|
||||
//!
|
||||
//! #[async_trait]
|
||||
//! impl RelayController for ModbusRelayController {
|
||||
//! async fn read_relay_state(&self, id: RelayId) -> Result<RelayState, ControllerError> {
|
||||
//! let address = self.config.base_address + id.value() as u16;
|
||||
//! let mut client = self.client.lock().await;
|
||||
//!
|
||||
//! // Read coil from Modbus device
|
||||
//! let result = client.read_coils(address, 1).await
|
||||
//! .map_err(|e| ControllerError::CommunicationError(e.to_string()))?;
|
||||
//!
|
||||
//! // Translate to domain type
|
||||
//! Ok(if result[0] { RelayState::On } else { RelayState::Off })
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! # References
|
||||
//!
|
||||
//! - Architecture: `specs/constitution.md` - Dependency Inversion Principle
|
||||
//! - Implementation: `specs/001-modbus-relay-control/plan.md` - Infrastructure tasks
|
||||
//! - Modbus docs: `docs/Modbus_POE_ETH_Relay.md` - Hardware protocol specification
|
||||
|
||||
pub mod persistence;
|
||||
7
backend/src/infrastructure/persistence/mod.rs
Normal file
7
backend/src/infrastructure/persistence/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
//! Persistence layer implementations.
|
||||
//!
|
||||
//! This module contains the concrete implementations of repository traits
|
||||
//! for data persistence, including SQLite-based storage for relay labels.
|
||||
|
||||
/// `SQLite` repository implementation for relay labels.
|
||||
pub mod sqlite_repository;
|
||||
64
backend/src/infrastructure/persistence/sqlite_repository.rs
Normal file
64
backend/src/infrastructure/persistence/sqlite_repository.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
use crate::domain::relay::repository::RepositoryError;
|
||||
|
||||
/// `SQLite` implementation of the relay label repository.
|
||||
///
|
||||
/// This repository manages persistent storage of relay labels using `SQLite`,
|
||||
/// with automatic schema migrations via `SQLx`.
|
||||
pub struct SqliteRelayLabelRepository {
|
||||
/// The `SQLite` connection pool for database operations.
|
||||
pool: SqlitePool,
|
||||
}
|
||||
|
||||
impl SqliteRelayLabelRepository {
|
||||
/// Creates a new `SQLite` relay label repository.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `db_path` - The `SQLite` database path or connection string (e.g., `"sqlite://data.db"`)
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `RepositoryError::DatabaseError` if the connection fails or migrations cannot be applied.
|
||||
pub async fn new(db_path: &str) -> Result<Self, RepositoryError> {
|
||||
let pool = SqlitePool::connect(db_path)
|
||||
.await
|
||||
.map_err(|e| RepositoryError::DatabaseError(e.to_string()))?;
|
||||
let repo = Self { pool };
|
||||
repo.run_migrations().await?;
|
||||
Ok(repo)
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying connection pool.
|
||||
///
|
||||
/// This is primarily used for testing to verify schema and constraints.
|
||||
#[must_use]
|
||||
pub const fn pool(&self) -> &SqlitePool {
|
||||
&self.pool
|
||||
}
|
||||
|
||||
/// Creates a new in-memory `SQLite` relay label repository.
|
||||
///
|
||||
/// This is useful for testing and ephemeral data storage.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `RepositoryError::DatabaseError` if the in-memory database cannot be created.
|
||||
pub async fn in_memory() -> Result<Self, RepositoryError> {
|
||||
Self::new("sqlite::memory:").await
|
||||
}
|
||||
|
||||
/// Runs all pending database migrations.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `RepositoryError::DatabaseError` if migrations fail to apply.
|
||||
async fn run_migrations(&self) -> Result<(), RepositoryError> {
|
||||
sqlx::migrate!("../migrations/")
|
||||
.run(&self.pool)
|
||||
.await
|
||||
.map_err(|e| RepositoryError::DatabaseError(e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user