diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a4420a..8d7e634 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Implemented roles & permissions. - Added "read-only user" role. - Added configuration option for automatic assigning of "read-only user" role after registration. +- Added `set-role` command. ### Deprecated diff --git a/docs/mitractl.md b/docs/mitractl.md index e2e49e5..9f33d9d 100644 --- a/docs/mitractl.md +++ b/docs/mitractl.md @@ -38,6 +38,12 @@ Set or change password: mitractl set-password ``` +Change user's role: + +```shell +mitractl set-role +``` + Delete profile: ```shell diff --git a/mitra-cli/src/cli.rs b/mitra-cli/src/cli.rs index fb13548..45ce02b 100644 --- a/mitra-cli/src/cli.rs +++ b/mitra-cli/src/cli.rs @@ -39,7 +39,9 @@ use mitra::models::{ get_invite_codes, get_user_by_id, set_user_password, + set_user_role, }, + users::types::Role, }; use mitra::monero::{ helpers::check_expired_invoice, @@ -70,6 +72,7 @@ pub enum SubCommand { GenerateInviteCode(GenerateInviteCode), ListInviteCodes(ListInviteCodes), SetPassword(SetPassword), + SetRole(SetRole), RefetchActor(RefetchActor), DeleteProfile(DeleteProfile), DeletePost(DeletePost), @@ -169,6 +172,25 @@ impl SetPassword { } } +/// Change user's role +#[derive(Parser)] +pub struct SetRole { + id: Uuid, + role: String, +} + +impl SetRole { + pub async fn execute( + &self, + db_client: &impl DatabaseClient, + ) -> Result<(), Error> { + let role = Role::from_name(&self.role)?; + set_user_role(db_client, &self.id, role).await?; + println!("role changed"); + Ok(()) + } +} + /// Re-fetch actor profile by actor ID #[derive(Parser)] pub struct RefetchActor { diff --git a/mitra-cli/src/main.rs b/mitra-cli/src/main.rs index c2a556f..50b54b7 100644 --- a/mitra-cli/src/main.rs +++ b/mitra-cli/src/main.rs @@ -32,6 +32,7 @@ async fn main() { SubCommand::GenerateInviteCode(cmd) => cmd.execute(db_client).await.unwrap(), SubCommand::ListInviteCodes(cmd) => cmd.execute(db_client).await.unwrap(), SubCommand::SetPassword(cmd) => cmd.execute(db_client).await.unwrap(), + SubCommand::SetRole(cmd) => cmd.execute(db_client).await.unwrap(), SubCommand::RefetchActor(cmd) => cmd.execute(&config, db_client).await.unwrap(), SubCommand::DeleteProfile(cmd) => cmd.execute(&config, db_client).await.unwrap(), SubCommand::DeletePost(cmd) => cmd.execute(&config, db_client).await.unwrap(), diff --git a/src/models/users/queries.rs b/src/models/users/queries.rs index 399c622..5d7323d 100644 --- a/src/models/users/queries.rs +++ b/src/models/users/queries.rs @@ -9,7 +9,7 @@ use crate::identity::{did::Did, did_pkh::DidPkh}; use crate::models::profiles::queries::create_profile; use crate::models::profiles::types::{DbActorProfile, ProfileCreateData}; use crate::utils::currencies::Currency; -use super::types::{DbUser, User, UserCreateData}; +use super::types::{DbUser, Role, User, UserCreateData}; use super::utils::generate_invite_code; pub async fn create_invite_code( @@ -154,6 +154,24 @@ pub async fn set_user_password( Ok(()) } +pub async fn set_user_role( + db_client: &impl DatabaseClient, + user_id: &Uuid, + role: Role, +) -> Result<(), DatabaseError> { + let updated_count = db_client.execute( + " + UPDATE user_account SET user_role = $1 + WHERE id = $2 + ", + &[&role, &user_id], + ).await?; + if updated_count == 0 { + return Err(DatabaseError::NotFound("user")); + }; + Ok(()) +} + pub async fn get_user_by_id( db_client: &impl DatabaseClient, user_id: &Uuid, @@ -307,4 +325,16 @@ mod tests { let result = create_user(db_client, another_user_data).await; assert!(matches!(result, Err(DatabaseError::AlreadyExists("user")))); } + + #[tokio::test] + #[serial] + async fn test_set_user_role() { + let db_client = &mut create_test_database().await; + let user_data = UserCreateData::default(); + let user = create_user(db_client, user_data).await.unwrap(); + assert_eq!(user.role, Role::NormalUser); + set_user_role(db_client, &user.id, Role::ReadOnlyUser).await.unwrap(); + let user = get_user_by_id(db_client, &user.id).await.unwrap(); + assert_eq!(user.role, Role::ReadOnlyUser); + } } diff --git a/src/models/users/types.rs b/src/models/users/types.rs index b8fbc8e..17c44d3 100644 --- a/src/models/users/types.rs +++ b/src/models/users/types.rs @@ -31,6 +31,16 @@ impl Default for Role { } impl Role { + pub fn from_name(name: &str) -> Result { + let role = match name { + "user" => Self::NormalUser, + "admin" => Self::Admin, + "read_only_user" => Self::ReadOnlyUser, + _ => return Err(ValidationError("unknown role")), + }; + Ok(role) + } + pub fn get_permissions(&self) -> Vec { match self { Self::Guest => vec![],