use super::document_box::DocumentBoxScopeRaw;
use crate::{DbExecutor, DbResult, models::document_box::DocumentBoxScopeRawRef};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::{Database, Decode, error::BoxDynError, postgres::PgQueryResult, prelude::FromRow};
use utoipa::ToSchema;
use uuid::Uuid;
pub type TaskId = Uuid;
#[derive(Debug, Clone, FromRow, Serialize, ToSchema)]
pub struct Task {
pub id: Uuid,
pub document_box: DocumentBoxScopeRaw,
pub status: TaskStatus,
pub output_data: Option<serde_json::Value>,
pub created_at: DateTime<Utc>,
pub completed_at: Option<DateTime<Utc>>,
}
impl Eq for Task {}
impl PartialEq for Task {
fn eq(&self, other: &Self) -> bool {
let complete_eq = match (&self.completed_at, &other.completed_at) {
(Some(a), Some(b)) => a.timestamp_millis().eq(&b.timestamp_millis()),
(None, None) => true,
_ => false,
};
self.id.eq(&other.id)
&& self.document_box.eq(&other.document_box)
&& self.status.eq(&other.status)
&& self.output_data.eq(&self.output_data)
&& self
.created_at
.timestamp_millis()
.eq(&other.created_at.timestamp_millis())
&& complete_eq
}
}
#[derive(
Debug,
Clone,
Copy,
strum::EnumString,
strum::Display,
Deserialize,
Serialize,
ToSchema,
PartialEq,
Eq,
)]
pub enum TaskStatus {
Pending,
Completed,
Failed,
}
impl<DB: Database> sqlx::Type<DB> for TaskStatus
where
String: sqlx::Type<DB>,
{
fn type_info() -> DB::TypeInfo {
String::type_info()
}
}
impl<'r, DB: Database> Decode<'r, DB> for TaskStatus
where
String: Decode<'r, DB>,
{
fn decode(value: <DB as Database>::ValueRef<'r>) -> Result<Self, BoxDynError> {
let value = <String as Decode<DB>>::decode(value)?;
Ok(value.parse()?)
}
}
impl Task {
pub async fn create(
db: impl DbExecutor<'_>,
document_box: DocumentBoxScopeRaw,
) -> DbResult<Task> {
let task_id = Uuid::new_v4();
let status = TaskStatus::Pending;
let created_at = Utc::now();
sqlx::query(
r#"
INSERT INTO "docbox_tasks" ("id", "document_box", "status", "created_at")
VALUES ($1, $2, $3, $4)
"#,
)
.bind(task_id)
.bind(document_box.as_str())
.bind(status.to_string())
.bind(created_at)
.execute(db)
.await?;
Ok(Task {
id: task_id,
document_box,
status,
output_data: None,
created_at,
completed_at: None,
})
}
pub async fn find(
db: impl DbExecutor<'_>,
id: TaskId,
document_box: DocumentBoxScopeRawRef<'_>,
) -> DbResult<Option<Task>> {
sqlx::query_as(r#"SELECT * FROM "docbox_tasks" WHERE "id" = $1 AND "document_box" = $2"#)
.bind(id)
.bind(document_box)
.fetch_optional(db)
.await
}
pub async fn complete_task(
&mut self,
db: impl DbExecutor<'_>,
status: TaskStatus,
output_data: Option<serde_json::Value>,
) -> DbResult<()> {
let completed_at = Utc::now();
sqlx::query(
r#"UPDATE "docbox_tasks" SET
"status" = $1,
"output_data" = $2,
"completed_at" = $3
WHERE "id" = $4"#,
)
.bind(status.to_string())
.bind(output_data.clone())
.bind(completed_at)
.bind(self.id)
.execute(db)
.await?;
self.status = status;
self.output_data = output_data.clone();
self.completed_at = Some(completed_at);
Ok(())
}
pub async fn delete_expired(
db: impl DbExecutor<'_>,
before: DateTime<Utc>,
) -> DbResult<PgQueryResult> {
sqlx::query(r#"DELETE FROM "docbox_tasks" WHERE "created_at" < $1"#)
.bind(before)
.execute(db)
.await
}
}