203
crates/adapters/sqlite/src/repositories/entry/command.rs
Normal file
203
crates/adapters/sqlite/src/repositories/entry/command.rs
Normal file
@@ -0,0 +1,203 @@
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
use domain::activity::ActivityId;
|
||||
use domain::entry::{DateRange, MoodEntry, MoodEntryId};
|
||||
use domain::errors::DomainError;
|
||||
use domain::user::UserId;
|
||||
|
||||
use super::super::shared::db_err;
|
||||
|
||||
pub struct SqliteEntryCommandRepository {
|
||||
pool: SqlitePool,
|
||||
}
|
||||
|
||||
impl SqliteEntryCommandRepository {
|
||||
pub fn new(pool: SqlitePool) -> Self {
|
||||
Self { pool }
|
||||
}
|
||||
|
||||
async fn save_relations(&self, entry: &MoodEntry) -> Result<(), DomainError> {
|
||||
let entry_id = entry.id().value().to_string();
|
||||
|
||||
sqlx::query("DELETE FROM entry_activities WHERE entry_id = ?")
|
||||
.bind(&entry_id)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(db_err)?;
|
||||
for activity_id in entry.activities() {
|
||||
sqlx::query("INSERT INTO entry_activities (entry_id, activity_id) VALUES (?, ?)")
|
||||
.bind(&entry_id)
|
||||
.bind(activity_id.value().to_string())
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(db_err)?;
|
||||
}
|
||||
|
||||
sqlx::query("DELETE FROM entry_photos WHERE entry_id = ?")
|
||||
.bind(&entry_id)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(db_err)?;
|
||||
for photo_id in entry.photos() {
|
||||
sqlx::query("INSERT INTO entry_photos (entry_id, photo_id) VALUES (?, ?)")
|
||||
.bind(&entry_id)
|
||||
.bind(photo_id.value().to_string())
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(db_err)?;
|
||||
}
|
||||
|
||||
sqlx::query("DELETE FROM entry_voice_memos WHERE entry_id = ?")
|
||||
.bind(&entry_id)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(db_err)?;
|
||||
for voice_memo_id in entry.voice_memos() {
|
||||
sqlx::query("INSERT INTO entry_voice_memos (entry_id, voice_memo_id) VALUES (?, ?)")
|
||||
.bind(&entry_id)
|
||||
.bind(voice_memo_id.value().to_string())
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(db_err)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl domain::ports::MoodEntryCommandPort for SqliteEntryCommandRepository {
|
||||
async fn save(&self, entry: &MoodEntry) -> Result<(), DomainError> {
|
||||
sqlx::query(
|
||||
"INSERT INTO mood_entries (id, user_id, mood, logged_at, content, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
mood = excluded.mood, logged_at = excluded.logged_at,
|
||||
content = excluded.content, updated_at = excluded.updated_at"
|
||||
)
|
||||
.bind(entry.id().value().to_string())
|
||||
.bind(entry.user_id().value().to_string())
|
||||
.bind(entry.mood().value() as i32)
|
||||
.bind(entry.logged_at().to_rfc3339())
|
||||
.bind(entry.content().map(|c| c.value().to_string()))
|
||||
.bind(entry.created_at().to_rfc3339())
|
||||
.bind(entry.updated_at().to_rfc3339())
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(db_err)?;
|
||||
|
||||
self.save_relations(entry).await
|
||||
}
|
||||
|
||||
async fn save_batch(&self, entries: &[MoodEntry]) -> Result<(), DomainError> {
|
||||
let mut tx = self.pool.begin().await.map_err(db_err)?;
|
||||
|
||||
for entry in entries {
|
||||
let entry_id = entry.id().value().to_string();
|
||||
|
||||
sqlx::query(
|
||||
"INSERT INTO mood_entries (id, user_id, mood, logged_at, content, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
mood = excluded.mood, logged_at = excluded.logged_at,
|
||||
content = excluded.content, updated_at = excluded.updated_at"
|
||||
)
|
||||
.bind(&entry_id)
|
||||
.bind(entry.user_id().value().to_string())
|
||||
.bind(entry.mood().value() as i32)
|
||||
.bind(entry.logged_at().to_rfc3339())
|
||||
.bind(entry.content().map(|c| c.value().to_string()))
|
||||
.bind(entry.created_at().to_rfc3339())
|
||||
.bind(entry.updated_at().to_rfc3339())
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.map_err(db_err)?;
|
||||
|
||||
for activity_id in entry.activities() {
|
||||
sqlx::query("INSERT INTO entry_activities (entry_id, activity_id) VALUES (?, ?)")
|
||||
.bind(&entry_id)
|
||||
.bind(activity_id.value().to_string())
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.map_err(db_err)?;
|
||||
}
|
||||
|
||||
for photo_id in entry.photos() {
|
||||
sqlx::query("INSERT INTO entry_photos (entry_id, photo_id) VALUES (?, ?)")
|
||||
.bind(&entry_id)
|
||||
.bind(photo_id.value().to_string())
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.map_err(db_err)?;
|
||||
}
|
||||
|
||||
for voice_memo_id in entry.voice_memos() {
|
||||
sqlx::query(
|
||||
"INSERT INTO entry_voice_memos (entry_id, voice_memo_id) VALUES (?, ?)",
|
||||
)
|
||||
.bind(&entry_id)
|
||||
.bind(voice_memo_id.value().to_string())
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.map_err(db_err)?;
|
||||
}
|
||||
}
|
||||
|
||||
tx.commit().await.map_err(db_err)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete(&self, id: &MoodEntryId) -> Result<(), DomainError> {
|
||||
sqlx::query("DELETE FROM mood_entries WHERE id = ?")
|
||||
.bind(id.value().to_string())
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(db_err)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_all_by_user(&self, user_id: &UserId) -> Result<(), DomainError> {
|
||||
sqlx::query("DELETE FROM mood_entries WHERE user_id = ?")
|
||||
.bind(user_id.value().to_string())
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(db_err)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_by_date_range(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
range: &DateRange,
|
||||
) -> Result<u64, DomainError> {
|
||||
let result = sqlx::query(
|
||||
"DELETE FROM mood_entries WHERE user_id = ? AND logged_at >= ? AND logged_at <= ?",
|
||||
)
|
||||
.bind(user_id.value().to_string())
|
||||
.bind(range.start().to_rfc3339())
|
||||
.bind(range.end().to_rfc3339())
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(db_err)?;
|
||||
Ok(result.rows_affected())
|
||||
}
|
||||
|
||||
async fn replace_activity(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
old_activity_id: &ActivityId,
|
||||
new_activity_id: &ActivityId,
|
||||
) -> Result<u64, DomainError> {
|
||||
let result = sqlx::query(
|
||||
"UPDATE entry_activities SET activity_id = ?
|
||||
WHERE activity_id = ? AND entry_id IN (SELECT id FROM mood_entries WHERE user_id = ?)",
|
||||
)
|
||||
.bind(new_activity_id.value().to_string())
|
||||
.bind(old_activity_id.value().to_string())
|
||||
.bind(user_id.value().to_string())
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(db_err)?;
|
||||
Ok(result.rows_affected())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user