aven-core 0.1.12

Core library for the Aven local-first task manager
use crate::ids::WorkspaceId;
use anyhow::{Result, bail};
use sqlx::SqliteConnection;

use crate::operations::dependency_path_exists;
use crate::sync::wire::ChangeWire;

use super::shared::{str_payload, task_id, workspace_id_payload};

pub(super) async fn add_dependency(conn: &mut SqliteConnection, change: &ChangeWire) -> Result<()> {
    let workspace_id = workspace_id_payload(conn, change).await?;
    let task_id = task_id(change)?;
    let depends_on_task_id: crate::ids::TaskId =
        str_payload(&change.payload, "depends_on_task_id")?.parse()?;
    ensure_dependency_tasks_exist(conn, &workspace_id, &task_id, &depends_on_task_id).await?;
    if dependency_path_exists(conn, &workspace_id, &depends_on_task_id, &task_id).await? {
        if !remote_dependency_wins(&task_id, &depends_on_task_id) {
            return Ok(());
        }
        sqlx::query(
            "DELETE FROM task_dependencies
             WHERE workspace_id = ? AND task_id = ? AND depends_on_task_id = ?",
        )
        .bind(&workspace_id)
        .bind(&depends_on_task_id)
        .bind(&task_id)
        .execute(&mut *conn)
        .await?;
        if dependency_path_exists(conn, &workspace_id, &depends_on_task_id, &task_id).await? {
            return Ok(());
        }
    }
    sqlx::query(
        "INSERT OR IGNORE INTO task_dependencies(workspace_id, task_id, depends_on_task_id, created_at)
         VALUES (?, ?, ?, ?)",
    )
    .bind(&workspace_id)
    .bind(&task_id)
    .bind(&depends_on_task_id)
    .bind(&change.created_at)
    .execute(&mut *conn)
    .await?;
    Ok(())
}

pub(super) async fn remove_dependency(
    conn: &mut SqliteConnection,
    change: &ChangeWire,
) -> Result<()> {
    let workspace_id = workspace_id_payload(conn, change).await?;
    let task_id = task_id(change)?;
    let depends_on_task_id: crate::ids::TaskId =
        str_payload(&change.payload, "depends_on_task_id")?.parse()?;
    sqlx::query(
        "DELETE FROM task_dependencies WHERE workspace_id = ? AND task_id = ? AND depends_on_task_id = ?",
    )
    .bind(&workspace_id)
    .bind(&task_id)
    .bind(&depends_on_task_id)
    .execute(&mut *conn)
    .await?;
    Ok(())
}

async fn ensure_dependency_tasks_exist(
    conn: &mut SqliteConnection,
    workspace_id: &WorkspaceId,
    task_id: &crate::ids::TaskId,
    depends_on_task_id: &crate::ids::TaskId,
) -> Result<()> {
    let existing: i64 = sqlx::query_scalar::<_, i64>(
        "SELECT count(*) FROM tasks WHERE workspace_id = ? AND id IN (?, ?)",
    )
    .bind(workspace_id)
    .bind(task_id)
    .bind(depends_on_task_id)
    .fetch_one(&mut *conn)
    .await?;
    if existing != 2 {
        bail!(
            "error dependency-missing-task task_id={task_id} depends_on_task_id={depends_on_task_id}"
        );
    }
    Ok(())
}

fn remote_dependency_wins(
    task_id: &crate::ids::TaskId,
    depends_on_task_id: &crate::ids::TaskId,
) -> bool {
    task_id < depends_on_task_id
}