refactor: group postgres adapters by bounded context

This commit is contained in:
2026-05-31 11:11:46 +02:00
parent a6b86c23d8
commit e082387f6e
21 changed files with 1891 additions and 1966 deletions

View File

@@ -0,0 +1,119 @@
use crate::db::PgPool;
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use domain::{
errors::DomainError,
ports::UserRepository,
value_objects::{Email, PasswordHash, SystemId},
};
use uuid::Uuid;
#[derive(sqlx::FromRow)]
struct UserRow {
id: Uuid,
username: String,
email: String,
password_hash: String,
created_at: DateTime<Utc>,
}
impl TryFrom<UserRow> for domain::entities::User {
type Error = DomainError;
fn try_from(r: UserRow) -> Result<Self, Self::Error> {
Ok(Self {
id: SystemId::from_uuid(r.id),
username: r.username,
email: Email::new(r.email)?,
password_hash: PasswordHash::from_hash(r.password_hash),
created_at: r.created_at,
})
}
}
pub struct PostgresUserRepository {
pool: PgPool,
}
impl PostgresUserRepository {
pub fn new(pool: PgPool) -> Self {
Self { pool }
}
}
#[async_trait]
impl UserRepository for PostgresUserRepository {
async fn find_by_id(
&self,
id: &SystemId,
) -> Result<Option<domain::entities::User>, DomainError> {
let row = sqlx::query_as::<_, UserRow>(
"SELECT id, username, email, password_hash, created_at FROM users WHERE id = $1",
)
.bind(*id.as_uuid())
.fetch_optional(&self.pool)
.await
.map_err(|e| DomainError::Internal(e.to_string()))?;
row.map(TryInto::try_into).transpose()
}
async fn find_by_email(
&self,
email: &Email,
) -> Result<Option<domain::entities::User>, DomainError> {
let row = sqlx::query_as::<_, UserRow>(
"SELECT id, username, email, password_hash, created_at FROM users WHERE email = $1",
)
.bind(email.as_str())
.fetch_optional(&self.pool)
.await
.map_err(|e| DomainError::Internal(e.to_string()))?;
row.map(TryInto::try_into).transpose()
}
async fn find_by_username(
&self,
username: &str,
) -> Result<Option<domain::entities::User>, DomainError> {
let row = sqlx::query_as::<_, UserRow>(
"SELECT id, username, email, password_hash, created_at FROM users WHERE username = $1",
)
.bind(username)
.fetch_optional(&self.pool)
.await
.map_err(|e| DomainError::Internal(e.to_string()))?;
row.map(TryInto::try_into).transpose()
}
async fn save(&self, user: &domain::entities::User) -> Result<(), DomainError> {
sqlx::query_as::<_, UserRow>(
"INSERT INTO users (id, username, email, password_hash, created_at)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (id) DO UPDATE SET
username = EXCLUDED.username,
email = EXCLUDED.email,
password_hash = EXCLUDED.password_hash
RETURNING id, username, email, password_hash, created_at",
)
.bind(*user.id.as_uuid())
.bind(&user.username)
.bind(user.email.as_str())
.bind(user.password_hash.as_str())
.bind(user.created_at)
.fetch_one(&self.pool)
.await
.map_err(|e| DomainError::Internal(e.to_string()))?;
Ok(())
}
async fn delete(&self, id: &SystemId) -> Result<(), DomainError> {
sqlx::query("DELETE FROM users WHERE id = $1")
.bind(*id.as_uuid())
.execute(&self.pool)
.await
.map_err(|e| DomainError::Internal(e.to_string()))?;
Ok(())
}
}