231 lines
7.5 KiB
Rust
231 lines
7.5 KiB
Rust
use chrono::{DateTime, Utc};
|
|
use serde::{Deserialize, Serialize};
|
|
use sqlx::{FromRow, SqlitePool, Type};
|
|
use ts_rs::TS;
|
|
use uuid::Uuid;
|
|
|
|
#[derive(Debug, Clone, Type, Serialize, Deserialize, PartialEq, TS)]
|
|
#[sqlx(type_name = "task_status", rename_all = "lowercase")]
|
|
#[serde(rename_all = "lowercase")]
|
|
#[ts(export)]
|
|
pub enum TaskStatus {
|
|
Todo,
|
|
InProgress,
|
|
InReview,
|
|
Done,
|
|
Cancelled,
|
|
}
|
|
|
|
#[derive(Debug, Clone, FromRow, Serialize, Deserialize, TS)]
|
|
#[ts(export)]
|
|
pub struct Task {
|
|
pub id: Uuid,
|
|
pub project_id: Uuid, // Foreign key to Project
|
|
pub title: String,
|
|
pub description: Option<String>,
|
|
pub status: TaskStatus,
|
|
pub created_at: DateTime<Utc>,
|
|
pub updated_at: DateTime<Utc>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
|
#[ts(export)]
|
|
pub struct TaskWithAttemptStatus {
|
|
pub id: Uuid,
|
|
pub project_id: Uuid,
|
|
pub title: String,
|
|
pub description: Option<String>,
|
|
pub status: TaskStatus,
|
|
pub created_at: DateTime<Utc>,
|
|
pub updated_at: DateTime<Utc>,
|
|
pub has_in_progress_attempt: bool,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, TS)]
|
|
#[ts(export)]
|
|
pub struct CreateTask {
|
|
pub project_id: Uuid,
|
|
pub title: String,
|
|
pub description: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, TS)]
|
|
#[ts(export)]
|
|
pub struct UpdateTask {
|
|
pub title: Option<String>,
|
|
pub description: Option<String>,
|
|
pub status: Option<TaskStatus>,
|
|
}
|
|
|
|
impl Task {
|
|
pub async fn find_by_project_id(
|
|
pool: &SqlitePool,
|
|
project_id: Uuid,
|
|
) -> Result<Vec<Self>, sqlx::Error> {
|
|
sqlx::query_as!(
|
|
Task,
|
|
r#"SELECT id as "id!: Uuid", project_id as "project_id!: Uuid", title, description, status as "status!: TaskStatus", created_at as "created_at!: DateTime<Utc>", updated_at as "updated_at!: DateTime<Utc>"
|
|
FROM tasks
|
|
WHERE project_id = $1
|
|
ORDER BY created_at DESC"#,
|
|
project_id
|
|
)
|
|
.fetch_all(pool)
|
|
.await
|
|
}
|
|
|
|
pub async fn find_by_project_id_with_attempt_status(
|
|
pool: &SqlitePool,
|
|
project_id: Uuid,
|
|
) -> Result<Vec<TaskWithAttemptStatus>, sqlx::Error> {
|
|
let records = sqlx::query!(
|
|
r#"SELECT
|
|
t.id as "id!: Uuid",
|
|
t.project_id as "project_id!: Uuid",
|
|
t.title,
|
|
t.description,
|
|
t.status as "status!: TaskStatus",
|
|
t.created_at as "created_at!: DateTime<Utc>",
|
|
t.updated_at as "updated_at!: DateTime<Utc>",
|
|
CASE WHEN in_progress_attempts.task_id IS NOT NULL THEN true ELSE false END as "has_in_progress_attempt!"
|
|
FROM tasks t
|
|
LEFT JOIN (
|
|
SELECT DISTINCT ta.task_id
|
|
FROM task_attempts ta
|
|
INNER JOIN (
|
|
SELECT task_attempt_id, MAX(created_at) as latest_created_at
|
|
FROM task_attempt_activities
|
|
GROUP BY task_attempt_id
|
|
) latest_activity ON ta.id = latest_activity.task_attempt_id
|
|
INNER JOIN task_attempt_activities taa ON ta.id = taa.task_attempt_id
|
|
AND taa.created_at = latest_activity.latest_created_at
|
|
WHERE taa.status = 'inprogress'
|
|
) in_progress_attempts ON t.id = in_progress_attempts.task_id
|
|
WHERE t.project_id = $1
|
|
ORDER BY t.created_at DESC"#,
|
|
project_id
|
|
)
|
|
.fetch_all(pool)
|
|
.await?;
|
|
|
|
let tasks = records
|
|
.into_iter()
|
|
.map(|record| TaskWithAttemptStatus {
|
|
id: record.id,
|
|
project_id: record.project_id,
|
|
title: record.title,
|
|
description: record.description,
|
|
status: record.status,
|
|
created_at: record.created_at,
|
|
updated_at: record.updated_at,
|
|
has_in_progress_attempt: record.has_in_progress_attempt != 0,
|
|
})
|
|
.collect();
|
|
|
|
Ok(tasks)
|
|
}
|
|
|
|
pub async fn find_by_id(pool: &SqlitePool, id: Uuid) -> Result<Option<Self>, sqlx::Error> {
|
|
sqlx::query_as!(
|
|
Task,
|
|
r#"SELECT id as "id!: Uuid", project_id as "project_id!: Uuid", title, description, status as "status!: TaskStatus", created_at as "created_at!: DateTime<Utc>", updated_at as "updated_at!: DateTime<Utc>"
|
|
FROM tasks
|
|
WHERE id = $1"#,
|
|
id
|
|
)
|
|
.fetch_optional(pool)
|
|
.await
|
|
}
|
|
|
|
pub async fn find_by_id_and_project_id(
|
|
pool: &SqlitePool,
|
|
id: Uuid,
|
|
project_id: Uuid,
|
|
) -> Result<Option<Self>, sqlx::Error> {
|
|
sqlx::query_as!(
|
|
Task,
|
|
r#"SELECT id as "id!: Uuid", project_id as "project_id!: Uuid", title, description, status as "status!: TaskStatus", created_at as "created_at!: DateTime<Utc>", updated_at as "updated_at!: DateTime<Utc>"
|
|
FROM tasks
|
|
WHERE id = $1 AND project_id = $2"#,
|
|
id,
|
|
project_id
|
|
)
|
|
.fetch_optional(pool)
|
|
.await
|
|
}
|
|
|
|
pub async fn create(
|
|
pool: &SqlitePool,
|
|
data: &CreateTask,
|
|
task_id: Uuid,
|
|
) -> Result<Self, sqlx::Error> {
|
|
sqlx::query_as!(
|
|
Task,
|
|
r#"INSERT INTO tasks (id, project_id, title, description, status)
|
|
VALUES ($1, $2, $3, $4, $5)
|
|
RETURNING id as "id!: Uuid", project_id as "project_id!: Uuid", title, description, status as "status!: TaskStatus", created_at as "created_at!: DateTime<Utc>", updated_at as "updated_at!: DateTime<Utc>""#,
|
|
task_id,
|
|
data.project_id,
|
|
data.title,
|
|
data.description,
|
|
TaskStatus::Todo as TaskStatus
|
|
)
|
|
.fetch_one(pool)
|
|
.await
|
|
}
|
|
|
|
pub async fn update(
|
|
pool: &SqlitePool,
|
|
id: Uuid,
|
|
project_id: Uuid,
|
|
title: String,
|
|
description: Option<String>,
|
|
status: TaskStatus,
|
|
) -> Result<Self, sqlx::Error> {
|
|
let status_value = status as TaskStatus;
|
|
sqlx::query_as!(
|
|
Task,
|
|
r#"UPDATE tasks
|
|
SET title = $3, description = $4, status = $5
|
|
WHERE id = $1 AND project_id = $2
|
|
RETURNING id as "id!: Uuid", project_id as "project_id!: Uuid", title, description, status as "status!: TaskStatus", created_at as "created_at!: DateTime<Utc>", updated_at as "updated_at!: DateTime<Utc>""#,
|
|
id,
|
|
project_id,
|
|
title,
|
|
description,
|
|
status_value
|
|
)
|
|
.fetch_one(pool)
|
|
.await
|
|
}
|
|
|
|
pub async fn delete(pool: &SqlitePool, id: Uuid, project_id: Uuid) -> Result<u64, sqlx::Error> {
|
|
let project_id_str = project_id.to_string();
|
|
let result = sqlx::query!(
|
|
"DELETE FROM tasks WHERE id = $1 AND project_id = $2",
|
|
id,
|
|
project_id_str
|
|
)
|
|
.execute(pool)
|
|
.await?;
|
|
Ok(result.rows_affected())
|
|
}
|
|
|
|
pub async fn exists(
|
|
pool: &SqlitePool,
|
|
id: Uuid,
|
|
project_id: Uuid,
|
|
) -> Result<bool, sqlx::Error> {
|
|
let id_str = id.to_string();
|
|
let project_id_str = project_id.to_string();
|
|
let result = sqlx::query!(
|
|
"SELECT id FROM tasks WHERE id = $1 AND project_id = $2",
|
|
id_str,
|
|
project_id_str
|
|
)
|
|
.fetch_optional(pool)
|
|
.await?;
|
|
Ok(result.is_some())
|
|
}
|
|
}
|