slave 0.4.0

Laravel-inspired CLI for Rust MCP servers: scaffold apps whose business operations are MCP tools over Streamable HTTP, with built-in auth, users, roles and Turso migrations
//! Persistence for users. Functions take a connection so callers can group
//! several of them in one transaction.

use slave_framework::turso::{Connection, Result, Row};

use crate::modules::user::models::user::User;

const COLUMNS: &str = "id, name, email, password_hash, created_at";

pub async fn all(conn: &Connection) -> Result<Vec<User>> {
    let mut rows = conn
        .query(format!("SELECT {COLUMNS} FROM users ORDER BY id"), ())
        .await?;
    let mut users = Vec::new();
    while let Some(row) = rows.next().await? {
        users.push(from_row(&row)?);
    }
    Ok(users)
}

pub async fn find(conn: &Connection, id: i64) -> Result<Option<User>> {
    let mut rows = conn
        .query(format!("SELECT {COLUMNS} FROM users WHERE id = ?1"), (id,))
        .await?;
    rows.next().await?.map(|row| from_row(&row)).transpose()
}

pub async fn find_by_email(conn: &Connection, email: &str) -> Result<Option<User>> {
    let mut rows = conn
        .query(
            format!("SELECT {COLUMNS} FROM users WHERE email = ?1"),
            (email,),
        )
        .await?;
    rows.next().await?.map(|row| from_row(&row)).transpose()
}

pub async fn count(conn: &Connection) -> Result<i64> {
    let mut rows = conn.query("SELECT count(*) FROM users", ()).await?;
    match rows.next().await? {
        Some(row) => row.get(0),
        None => Ok(0),
    }
}

pub async fn insert(
    conn: &Connection,
    name: &str,
    email: &str,
    password_hash: &str,
) -> Result<User> {
    let mut rows = conn
        .query(
            format!(
                "INSERT INTO users (name, email, password_hash) VALUES (?1, ?2, ?3) RETURNING {COLUMNS}"
            ),
            (name, email, password_hash),
        )
        .await?;
    match rows.next().await? {
        Some(row) => from_row(&row),
        None => Err(slave_framework::turso::Error::QueryReturnedNoRows),
    }
}

/// Deletes a user; their role assignments and tokens are removed by cascade.
pub async fn delete(conn: &Connection, id: i64) -> Result<bool> {
    Ok(conn
        .execute("DELETE FROM users WHERE id = ?1", (id,))
        .await?
        > 0)
}

fn from_row(row: &Row) -> Result<User> {
    Ok(User {
        id: row.get(0)?,
        name: row.get(1)?,
        email: row.get(2)?,
        password_hash: row.get(3)?,
        created_at: row.get(4)?,
    })
}