//! Bot API token persistence. `token_hash` is sha256 of the raw bearer //! token; the raw value is shown to the user once at creation and never //! stored. use sqlx::PgPool; use uuid::Uuid; use crate::domain::ApiToken; use crate::error::AppResult; pub async fn create( pool: &PgPool, user_id: Uuid, name: &str, token_hash: &[u8], ) -> AppResult { let row = sqlx::query_as::<_, ApiToken>( r#" INSERT INTO api_tokens (user_id, name, token_hash) VALUES ($1, $2, $3) RETURNING id, user_id, name, token_hash, created_at, last_used_at "#, ) .bind(user_id) .bind(name) .bind(token_hash) .fetch_one(pool) .await?; Ok(row) } pub async fn find_active(pool: &PgPool, token_hash: &[u8]) -> AppResult> { let row = sqlx::query_as::<_, ApiToken>( r#" SELECT id, user_id, name, token_hash, created_at, last_used_at FROM api_tokens WHERE token_hash = $1 "#, ) .bind(token_hash) .fetch_optional(pool) .await?; Ok(row) } pub async fn touch_last_used(pool: &PgPool, id: Uuid) -> AppResult<()> { sqlx::query("UPDATE api_tokens SET last_used_at = now() WHERE id = $1") .bind(id) .execute(pool) .await?; Ok(()) } pub async fn find_owner(pool: &PgPool, id: Uuid) -> AppResult> { let row: Option<(Uuid,)> = sqlx::query_as("SELECT user_id FROM api_tokens WHERE id = $1") .bind(id) .fetch_optional(pool) .await?; Ok(row.map(|(uid,)| uid)) } pub async fn delete(pool: &PgPool, id: Uuid) -> AppResult<()> { sqlx::query("DELETE FROM api_tokens WHERE id = $1") .bind(id) .execute(pool) .await?; Ok(()) }