use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use sqlx::{Pool, Row, Sqlite}; use uuid::Uuid; use crate::services::AppError; #[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy, sqlx::Type)] #[serde(rename_all = "snake_case")] #[sqlx(rename_all = "lowercase")] pub enum ProjectStatus { Active, Done, Backlog, } #[derive(sqlx::FromRow, Serialize)] pub struct ProjectModel { pub id: Uuid, pub title: String, pub color: String, pub folder_id: Option, pub sort_order: i64, pub status: ProjectStatus, pub created_at: DateTime, pub updated_at: DateTime, pub completed_at: Option>, } impl ProjectModel { async fn get_new_sort_order(pool: &Pool) -> anyhow::Result { let result = sqlx::query!("SELECT MAX(sort_order) as max_order FROM projects;") .fetch_one(pool) .await?; match result.max_order { Some(i) => Ok(i + 1000), None => Ok(0), } } pub async fn insert( pool: &Pool, title: String, color: String, ) -> anyhow::Result { if title.is_empty() { return Err(AppError::Unprocessable( "Title must not be empty".to_string(), )); } if color.len() != 7 || color.chars().next() != Some('#') { return Err(AppError::Unprocessable( "Color is not a valid hex".to_string(), )); } let id = Uuid::new_v4(); let sort_order = ProjectModel::get_new_sort_order(pool).await?; let result = sqlx::query_as( "INSERT INTO projects(id, title, color, sort_order) VALUES($1, $2, $3, $4) RETURNING *", ) .bind(id) .bind(title) .bind(color) .bind(sort_order) .fetch_one(pool) .await?; Ok(result) } pub async fn get_by_id( pool: &Pool, id: Uuid, ) -> anyhow::Result { sqlx::query_as("SELECT * FROM projects WHERE id = $1") .bind(id) .fetch_one(pool) .await .map_err(|e| match e { sqlx::Error::RowNotFound => AppError::NotFound, e => e.into(), }) } pub async fn list(pool: &Pool) -> anyhow::Result> { sqlx::query_as("SELECT * FROM projects;") .fetch_all(pool) .await .map_err(|e| e.into()) } pub async fn update(self, pool: &Pool) -> anyhow::Result { let now: DateTime = Utc::now(); let _ = sqlx::query!( "UPDATE projects SET title=$1, color=$2, folder_id=$3, status=$4, sort_order=$5, updated_at=$6, completed_at=$7 WHERE id=$8", self.title, self.color, self.folder_id, self.status, self.sort_order, now, self.completed_at, self.id, ) .execute(pool) .await?; ProjectModel::get_by_id(pool, self.id).await } pub async fn delete(pool: &Pool, id: Uuid) -> anyhow::Result<()> { sqlx::query!("DELETE FROM projects WHERE id = $1", id) .execute(pool) .await?; Ok(()) } }