Refactor schedule and user repositories into modular structure

- Moved schedule repository logic into separate modules for SQLite and PostgreSQL implementations.
- Created a mapping module for shared data structures and mapping functions in the schedule repository.
- Added new mapping module for user repository to handle user data transformations.
- Implemented PostgreSQL and SQLite user repository adapters with necessary CRUD operations.
- Added tests for user repository functionality, including saving, finding, and deleting users.
This commit is contained in:
2026-03-13 01:35:14 +01:00
parent 79ced7b77b
commit eeb4e2cb41
39 changed files with 2288 additions and 2194 deletions

View File

@@ -0,0 +1,100 @@
use async_trait::async_trait;
use domain::{Channel, ChannelId, ChannelRepository, DomainError, DomainResult, UserId};
use super::mapping::{ChannelRow, SELECT_COLS};
pub struct SqliteChannelRepository {
pool: sqlx::SqlitePool,
}
impl SqliteChannelRepository {
pub fn new(pool: sqlx::SqlitePool) -> Self {
Self { pool }
}
}
#[async_trait]
impl ChannelRepository for SqliteChannelRepository {
async fn find_by_id(&self, id: ChannelId) -> DomainResult<Option<Channel>> {
let sql = format!("SELECT {SELECT_COLS} FROM channels WHERE id = ?");
let row: Option<ChannelRow> = sqlx::query_as(&sql)
.bind(id.to_string())
.fetch_optional(&self.pool)
.await
.map_err(|e| DomainError::RepositoryError(e.to_string()))?;
row.map(Channel::try_from).transpose()
}
async fn find_by_owner(&self, owner_id: UserId) -> DomainResult<Vec<Channel>> {
let sql = format!(
"SELECT {SELECT_COLS} FROM channels WHERE owner_id = ? ORDER BY created_at ASC"
);
let rows: Vec<ChannelRow> = sqlx::query_as(&sql)
.bind(owner_id.to_string())
.fetch_all(&self.pool)
.await
.map_err(|e| DomainError::RepositoryError(e.to_string()))?;
rows.into_iter().map(Channel::try_from).collect()
}
async fn find_all(&self) -> DomainResult<Vec<Channel>> {
let sql = format!("SELECT {SELECT_COLS} FROM channels ORDER BY created_at ASC");
let rows: Vec<ChannelRow> = sqlx::query_as(&sql)
.fetch_all(&self.pool)
.await
.map_err(|e| DomainError::RepositoryError(e.to_string()))?;
rows.into_iter().map(Channel::try_from).collect()
}
async fn save(&self, channel: &Channel) -> DomainResult<()> {
let schedule_config = serde_json::to_string(&channel.schedule_config).map_err(|e| {
DomainError::RepositoryError(format!("Failed to serialize schedule_config: {}", e))
})?;
let recycle_policy = serde_json::to_string(&channel.recycle_policy).map_err(|e| {
DomainError::RepositoryError(format!("Failed to serialize recycle_policy: {}", e))
})?;
sqlx::query(
r#"
INSERT INTO channels
(id, owner_id, name, description, timezone, schedule_config, recycle_policy, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET
name = excluded.name,
description = excluded.description,
timezone = excluded.timezone,
schedule_config = excluded.schedule_config,
recycle_policy = excluded.recycle_policy,
updated_at = excluded.updated_at
"#,
)
.bind(channel.id.to_string())
.bind(channel.owner_id.to_string())
.bind(&channel.name)
.bind(&channel.description)
.bind(&channel.timezone)
.bind(&schedule_config)
.bind(&recycle_policy)
.bind(channel.created_at.to_rfc3339())
.bind(channel.updated_at.to_rfc3339())
.execute(&self.pool)
.await
.map_err(|e| DomainError::RepositoryError(e.to_string()))?;
Ok(())
}
async fn delete(&self, id: ChannelId) -> DomainResult<()> {
sqlx::query("DELETE FROM channels WHERE id = ?")
.bind(id.to_string())
.execute(&self.pool)
.await
.map_err(|e| DomainError::RepositoryError(e.to_string()))?;
Ok(())
}
}