use chrono::{FixedOffset, Local};
use sea_orm::{
ActiveModelTrait, DatabaseConnection, EntityTrait, QueryFilter, QueryOrder,
sea_query::Expr,
};
use uuid::Uuid;
use crate::domain::priority::Priority;
use crate::domain::TodoStatus;
use crate::entity::todo_item::{self, Entity as TodoItem, Model};
use crate::error::AppError;
use chrono::DateTime;
fn local_now() -> chrono::DateTime<FixedOffset> {
Local::now().fixed_offset()
}
pub struct TodoRepository {
db: DatabaseConnection,
}
impl TodoRepository {
pub fn new(db: DatabaseConnection) -> Self {
Self { db }
}
pub async fn find_active(&self) -> Result<Vec<Model>, AppError> {
TodoItem::find()
.filter(Expr::col(todo_item::Column::DeletedAt).is_null())
.order_by_desc(todo_item::Column::Status)
.order_by_asc(todo_item::Column::Priority)
.order_by_desc(todo_item::Column::CreatedAt)
.all(&self.db)
.await
.map_err(AppError::Db)
}
pub async fn find_active_by_time(&self) -> Result<Vec<Model>, AppError> {
TodoItem::find()
.filter(Expr::col(todo_item::Column::DeletedAt).is_null())
.order_by_desc(todo_item::Column::CreatedAt)
.all(&self.db)
.await
.map_err(AppError::Db)
}
pub async fn find_by_id(&self, id: &str) -> Result<Option<Model>, AppError> {
TodoItem::find_by_id(id)
.filter(Expr::col(todo_item::Column::DeletedAt).is_null())
.one(&self.db)
.await
.map_err(AppError::Db)
}
pub async fn create(&self, title: &str, note: &str) -> Result<Model, AppError> {
let now = local_now();
let id = Uuid::new_v4().to_string();
let active = todo_item::ActiveModel {
id: sea_orm::Set(id.clone()),
title: sea_orm::Set(title.to_string()),
note: sea_orm::Set(note.to_string()),
status: sea_orm::Set(TodoStatus::PENDING.to_string()),
priority: sea_orm::Set(Priority::NONE.into()),
created_at: sea_orm::Set(now),
updated_at: sea_orm::Set(now),
completed_at: sea_orm::Set(None),
deleted_at: sea_orm::Set(None),
};
TodoItem::insert(active).exec(&self.db).await.map_err(AppError::Db)?;
self.find_by_id(&id)
.await?
.ok_or_else(|| AppError::NotFound(id))
}
pub async fn toggle_status(&self, id: &str) -> Result<Model, AppError> {
let item = self
.find_by_id(id)
.await?
.ok_or_else(|| AppError::NotFound(id.to_string()))?;
let now = local_now();
let (new_status, completed_at) = if item.status == TodoStatus::PENDING {
(TodoStatus::COMPLETED.to_string(), Some(now))
} else {
(TodoStatus::PENDING.to_string(), None)
};
let mut active: todo_item::ActiveModel = item.into();
active.status = sea_orm::Set(new_status);
active.updated_at = sea_orm::Set(now);
active.completed_at = sea_orm::Set(completed_at);
active.update(&self.db).await.map_err(AppError::Db)
}
pub async fn update_todo(&self, id: &str, title: &str, note: &str) -> Result<Model, AppError> {
let item = self
.find_by_id(id)
.await?
.ok_or_else(|| AppError::NotFound(id.to_string()))?;
let now = local_now();
let mut active: todo_item::ActiveModel = item.into();
active.title = sea_orm::Set(title.to_string());
active.note = sea_orm::Set(note.to_string());
active.updated_at = sea_orm::Set(now);
active.update(&self.db).await.map_err(AppError::Db)
}
pub async fn set_priority(&self, id: &str, priority: i32) -> Result<Model, AppError> {
let item = self
.find_by_id(id)
.await?
.ok_or_else(|| AppError::NotFound(id.to_string()))?;
let now = local_now();
let mut active: todo_item::ActiveModel = item.into();
active.priority = sea_orm::Set(priority);
active.updated_at = sea_orm::Set(now);
active.update(&self.db).await.map_err(AppError::Db)
}
pub async fn find_completed_since(
&self,
since: DateTime<FixedOffset>,
) -> Result<Vec<Model>, AppError> {
TodoItem::find()
.filter(Expr::col(todo_item::Column::DeletedAt).is_null())
.filter(Expr::col(todo_item::Column::CompletedAt).is_not_null())
.filter(Expr::col(todo_item::Column::CompletedAt).gte(since))
.order_by_desc(todo_item::Column::CompletedAt)
.all(&self.db)
.await
.map_err(AppError::Db)
}
pub async fn soft_delete(&self, id: &str) -> Result<(), AppError> {
let item = self
.find_by_id(id)
.await?
.ok_or_else(|| AppError::NotFound(id.to_string()))?;
let now = local_now();
let mut active: todo_item::ActiveModel = item.into();
active.deleted_at = sea_orm::Set(Some(now));
active.update(&self.db).await.map_err(AppError::Db)?;
Ok(())
}
}