feat: init
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
//! 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<Option<Self>, 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<Option<Self>, 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<i64, sqlx::Error> {
|
||||
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<bool, sqlx::Error> {
|
||||
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<Option<Role>, sqlx::Error> {
|
||||
if owner_id == user_id {
|
||||
return Ok(Some(Role::Owner));
|
||||
}
|
||||
let role_str: Option<String> = 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::<Role>().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<bool, sqlx::Error> {
|
||||
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
|
||||
))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user