//! SQL query methods for the `Workspace` entity. use sqlx::PgPool; use uuid::Uuid; use super::workspace::Workspace; use crate::models::common::{Role, Visibility}; impl Workspace { /// Find a non-deleted workspace by primary key. pub async fn find_by_name(pool: &PgPool, name: &str) -> Result, sqlx::Error> { sqlx::query_as::<_, Workspace>( "SELECT id, owner_id, name, description, avatar_url, visibility, plan, status, \ default_role, is_personal, archived_at, created_at, updated_at, deleted_at \ FROM workspace WHERE name = $1 AND deleted_at IS NULL", ) .bind(name) .fetch_optional(pool) .await } pub async fn find_by_id(pool: &PgPool, id: Uuid) -> Result, sqlx::Error> { sqlx::query_as::<_, Workspace>( r#"SELECT id, owner_id, name, description, avatar_url, visibility, plan, status, default_role, is_personal, archived_at, created_at, updated_at, deleted_at FROM workspace WHERE id = $1 AND deleted_at IS NULL"#, ) .bind(id) .fetch_optional(pool) .await } /// Count non-deleted workspaces owned by a user. pub async fn count_owned(pool: &PgPool, owner_id: Uuid) -> Result { sqlx::query_scalar( r#"SELECT COUNT(*) FROM workspace WHERE owner_id = $1 AND deleted_at IS NULL"#, ) .bind(owner_id) .fetch_one(pool) .await } /// Check if a user is an active member of a workspace. pub async fn is_member( pool: &PgPool, workspace_id: Uuid, user_id: Uuid, ) -> Result { sqlx::query_scalar::<_, bool>( r#"SELECT EXISTS( SELECT 1 FROM workspace_member WHERE workspace_id = $1 AND user_id = $2 AND status = 'active' )"#, ) .bind(workspace_id) .bind(user_id) .fetch_one(pool) .await } /// Get the role of a user in a workspace. /// Returns `Role::Owner` if the user is the workspace owner but has no explicit member row. pub async fn user_role( pool: &PgPool, workspace_id: Uuid, user_id: Uuid, owner_id: Uuid, ) -> Result, sqlx::Error> { if owner_id == user_id { return Ok(Some(Role::Owner)); } let role_str: Option = sqlx::query_scalar( r#"SELECT role FROM workspace_member WHERE workspace_id = $1 AND user_id = $2 AND status = 'active'"#, ) .bind(workspace_id) .bind(user_id) .fetch_optional(pool) .await?; Ok(role_str.and_then(|r| r.parse::().ok())) } /// Check if the user can read the workspace. /// Readable if: owner, active member, or workspace is public/internal. pub async fn is_readable( pool: &PgPool, ws: &Workspace, user_id: Uuid, ) -> Result { if ws.owner_id == user_id { return Ok(true); } if Self::is_member(pool, ws.id, user_id).await? { return Ok(true); } Ok(matches!( ws.visibility, Visibility::Public | Visibility::Internal )) } }