use std::str::FromStr; use derive_builder::Builder; use serde::{Deserialize, Serialize}; use sqlx::{Decode, Postgres, query_as}; use strum::EnumString; use uuid::Uuid; use crate::client::AppDbClient; /// 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 WorkspaceUserPerm { /// 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_name: String, /// User to which the permission belongs. pub user_id: Uuid, /// Permission assigned to the user (currently only "connect"). pub perm: PermissionValue, } impl WorkspaceUserPerm { /// 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 insert statement to create a new object. pub fn insert() -> InsertBuilder { InsertBuilder::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!( WorkspaceUserPerm, r#" select p.id as id, p.workspace_id as workspace_id, p.user_id as user_id, p.perm as "perm: PermissionValue", w.name as workspace_name from workspace_user_perms 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)] pub struct Insert { workspace_id: Uuid, user_id: Uuid, perm: PermissionValue, } impl Insert { pub async fn execute(self, app_db: &mut AppDbClient) -> Result { query_as!( WorkspaceUserPerm, r#" with p as ( insert into workspace_user_perms (workspace_id, user_id, perm) values ($1, $2, $3) returning id, workspace_id, user_id, perm ) select p.id as id, p.workspace_id as workspace_id, p.user_id as user_id, p.perm as "perm: PermissionValue", w.name as workspace_name from workspace_user_perms as p inner join workspaces as w on w.id = p.workspace_id "#, self.workspace_id, self.user_id, self.perm.to_string(), ) .fetch_one(app_db.get_conn()) .await } } // TODO: The sqlx::Decode derive macro doesn't follow the strum serialization. // Does sqlx::Encode? #[derive(Clone, Debug, Deserialize, EnumString, PartialEq, Serialize, strum::Display)] #[serde(rename = "snake_case")] #[strum(serialize_all = "snake_case")] pub enum PermissionValue { Connect, } impl Decode<'_, Postgres> for PermissionValue { fn decode( value: ::ValueRef<'_>, ) -> Result { let value = <&str as Decode>::decode(value)?; Ok(Self::from_str(value)?) } }