use derive_builder::Builder; use serde::{Deserialize, Serialize}; use sqlx::{query, query_as}; use uuid::Uuid; use crate::{ client::AppDbClient, errors::{QueryError, QueryResult}, }; /// Assigns an access control permission on a workspace to a user. These are /// derived from the permission grants of the workspace's backing database. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct WorkspaceMembership { /// Primary key (defaults to UUIDv7). pub id: Uuid, /// Workspace to which the permission belongs. pub workspace_id: Uuid, /// **Synthesized field** generated by joining to the `workspaces` table. pub workspace_display_name: String, /// User to which the permission belongs. pub user_id: Uuid, } impl WorkspaceMembership { /// Construct a single-field query to fetch workspace permissions assigned /// to a user. pub fn belonging_to_user(id: Uuid) -> BelongingToUserQuery { BelongingToUserQuery { id } } /// Build an upsert statement to create a new object. If the new object is a /// duplicate, no update is performed. pub fn upsert() -> UpsertBuilder { UpsertBuilder::default() } /// Build a delete statement to remove the matching record if it exists. pub fn delete() -> DeleteBuilder { DeleteBuilder::default() } } #[derive(Clone, Debug)] pub struct BelongingToUserQuery { id: Uuid, } impl BelongingToUserQuery { pub async fn fetch_all( self, app_db: &mut AppDbClient, ) -> Result, sqlx::Error> { query_as!( WorkspaceMembership, r#" select p.id as id, p.workspace_id as workspace_id, p.user_id as user_id, w.display_name as workspace_display_name from workspace_memberships as p inner join workspaces as w on w.id = p.workspace_id where p.user_id = $1 "#, self.id, ) .fetch_all(app_db.get_conn()) .await } } #[derive(Builder, Clone, Debug)] #[builder(build_fn(error = "QueryError"), vis = "pub", pattern = "owned")] struct Upsert { workspace_id: Uuid, user_id: Uuid, } impl UpsertBuilder { pub async fn execute(self, app_db: &mut AppDbClient) -> QueryResult<()> { // To circumvent performance and complexity concerns, this method does not // return the upserted record. Refer to: // https://stackoverflow.com/a/42217872 let spec = self.build()?; query!( r#" insert into workspace_memberships (workspace_id, user_id) values ($1, $2) on conflict (workspace_id, user_id) do nothing "#, spec.workspace_id, spec.user_id, ) .execute(app_db.get_conn()) .await?; Ok(()) } } #[derive(Builder, Clone, Debug)] #[builder(build_fn(error = "QueryError"), vis = "pub", pattern = "owned")] pub struct Delete { workspace_id: Uuid, user_id: Uuid, } impl DeleteBuilder { pub async fn execute(self, app_db: &mut AppDbClient) -> QueryResult<()> { let spec = self.build()?; query!( r#" delete from workspace_memberships where workspace_id = $1 and user_id = $2 "#, spec.workspace_id, spec.user_id, ) .execute(app_db.get_conn()) .await?; Ok(()) } }