From 8027f135bcc31e0ab31f2d9c39e8ca2ca6857c7b Mon Sep 17 00:00:00 2001 From: mdecimus Date: Sun, 18 Feb 2024 16:48:44 +0100 Subject: [PATCH] Updated REST principal API --- crates/cli/src/modules/account.rs | 58 ++++++----- crates/cli/src/modules/cli.rs | 18 ++-- crates/cli/src/modules/database.rs | 12 +-- crates/cli/src/modules/domain.rs | 8 +- crates/cli/src/modules/group.rs | 20 ++-- crates/cli/src/modules/list.rs | 18 ++-- crates/cli/src/modules/queue.rs | 11 +-- crates/cli/src/modules/report.rs | 8 +- .../directory/src/backend/internal/manage.rs | 96 ++++++++++++------- crates/jmap/src/api/admin.rs | 76 +++++++++++---- crates/jmap/src/api/http.rs | 2 +- tests/resources/scripts/create_test_users.sh | 27 +++--- tests/src/directory/internal.rs | 20 ++-- 13 files changed, 224 insertions(+), 150 deletions(-) diff --git a/crates/cli/src/modules/account.rs b/crates/cli/src/modules/account.rs index c03881e4e..0ae35f686 100644 --- a/crates/cli/src/modules/account.rs +++ b/crates/cli/src/modules/account.rs @@ -61,7 +61,7 @@ impl AccountCommands { ..Default::default() }; let account_id = client - .http_request::(Method::POST, "/admin/principal", Some(principal)) + .http_request::(Method::POST, "/api/principal", Some(principal)) .await; eprintln!("Successfully created account {name:?} with id {account_id}."); } @@ -131,7 +131,7 @@ impl AccountCommands { client .http_request::( Method::PATCH, - &format!("/admin/principal/{name}"), + &format!("/api/principal/{name}"), Some(changes), ) .await; @@ -144,7 +144,7 @@ impl AccountCommands { client .http_request::( Method::PATCH, - &format!("/admin/principal/{name}"), + &format!("/api/principal/{name}"), Some( addresses .into_iter() @@ -164,7 +164,7 @@ impl AccountCommands { client .http_request::( Method::PATCH, - &format!("/admin/principal/{name}"), + &format!("/api/principal/{name}"), Some( addresses .into_iter() @@ -184,7 +184,7 @@ impl AccountCommands { client .http_request::( Method::PATCH, - &format!("/admin/principal/{name}"), + &format!("/api/principal/{name}"), Some( member_of .into_iter() @@ -204,7 +204,7 @@ impl AccountCommands { client .http_request::( Method::PATCH, - &format!("/admin/principal/{name}"), + &format!("/api/principal/{name}"), Some( member_of .into_iter() @@ -224,7 +224,7 @@ impl AccountCommands { client .http_request::( Method::DELETE, - &format!("/admin/principal/{name}"), + &format!("/api/principal/{name}"), None, ) .await; @@ -233,9 +233,13 @@ impl AccountCommands { AccountCommands::Display { name } => { client.display_principal(&name).await; } - AccountCommands::List { from, limit } => { + AccountCommands::List { + filter, + limit, + page, + } => { client - .list_principals("individual", "Account", from, limit) + .list_principals("individual", "Account", filter, page, limit) .await; } } @@ -245,11 +249,7 @@ impl AccountCommands { impl Client { pub async fn display_principal(&self, name: &str) { let principal = self - .http_request::( - Method::GET, - &format!("/admin/principal/{name}"), - None, - ) + .http_request::(Method::GET, &format!("/api/principal/{name}"), None) .await; let mut table = Table::new(); if let Some(name) = principal.name { @@ -318,31 +318,35 @@ impl Client { &self, record_type: &str, record_name: &str, - from: Option, + filter: Option, + page: Option, limit: Option, ) { - let mut query = form_urlencoded::Serializer::new("/admin/principal?".to_string()); + let mut query = form_urlencoded::Serializer::new("/api/principal?".to_string()); query.append_pair("type", record_type); - if let Some(from) = &from { - query.append_pair("from", from); + if let Some(filter) = &filter { + query.append_pair("filter", filter); } if let Some(limit) = limit { query.append_pair("limit", &limit.to_string()); } + if let Some(page) = page { + query.append_pair("page", &page.to_string()); + } let results = self - .http_request::, String>(Method::GET, &query.finish(), None) + .http_request::(Method::GET, &query.finish(), None) .await; - if !results.is_empty() { + if !results.items.is_empty() { let mut table = Table::new(); table.add_row(Row::new(vec![ Cell::new(&format!("{record_name} Name")).with_style(Attr::Bold) ])); - for domain in &results { - table.add_row(Row::new(vec![Cell::new(domain)])); + for item in &results.items { + table.add_row(Row::new(vec![Cell::new(item)])); } eprintln!(); @@ -352,13 +356,19 @@ impl Client { eprintln!( "\n\n{} {}{} found.\n", - results.len(), + results.total, record_name.to_ascii_lowercase(), - if results.len() == 1 { "" } else { "s" } + if results.total == 1 { "" } else { "s" } ); } } +#[derive(Debug, serde::Deserialize)] +struct ListResponse { + pub total: usize, + pub items: Vec, +} + impl Display for Type { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { diff --git a/crates/cli/src/modules/cli.rs b/crates/cli/src/modules/cli.rs index b5ffbb678..f0ccdc6ea 100644 --- a/crates/cli/src/modules/cli.rs +++ b/crates/cli/src/modules/cli.rs @@ -190,10 +190,12 @@ pub enum AccountCommands { /// List all user accounts List { - /// Starting point for listing accounts - from: Option, + /// Filter accounts by keywords + filter: Option, /// Maximum number of accounts to list limit: Option, + /// Page number + page: Option, }, } @@ -255,10 +257,12 @@ pub enum ListCommands { /// List all mailing lists List { - /// Starting point for listing mailing lists - from: Option, + /// Filter mailing lists by keywords + filter: Option, /// Maximum number of mailing lists to list limit: Option, + /// Page number + page: Option, }, } @@ -320,10 +324,12 @@ pub enum GroupCommands { /// List all groups List { - /// Starting point for listing groups - from: Option, + /// Filter groups by keywords + filter: Option, /// Maximum number of groups to list limit: Option, + /// Page number + page: Option, }, } diff --git a/crates/cli/src/modules/database.rs b/crates/cli/src/modules/database.rs index 44d0bb0df..7a3c0f27d 100644 --- a/crates/cli/src/modules/database.rs +++ b/crates/cli/src/modules/database.rs @@ -32,19 +32,19 @@ impl ServerCommands { match self { ServerCommands::DatabaseMaintenance {} => { client - .http_request::(Method::GET, "/admin/store/maintenance", None) + .http_request::(Method::GET, "/api/store/maintenance", None) .await; eprintln!("Success."); } ServerCommands::ReloadCertificates {} => { client - .http_request::(Method::GET, "/admin/reload/certificates", None) + .http_request::(Method::GET, "/api/reload/certificates", None) .await; eprintln!("Success."); } ServerCommands::ReloadConfig {} => { client - .http_request::(Method::GET, "/admin/reload/config", None) + .http_request::(Method::GET, "/api/reload/config", None) .await; eprintln!("Success."); } @@ -52,7 +52,7 @@ impl ServerCommands { client .http_request::( Method::POST, - "/admin/config", + "/api/config", Some(vec![(key.clone(), value.unwrap_or_default())]), ) .await; @@ -62,7 +62,7 @@ impl ServerCommands { client .http_request::( Method::DELETE, - &format!("/admin/config/{key}"), + &format!("/api/config/{key}"), None, ) .await; @@ -72,7 +72,7 @@ impl ServerCommands { let results = client .http_request::, String>( Method::GET, - &format!("/admin/config/{}", prefix.unwrap_or_default()), + &format!("/api/config/{}", prefix.unwrap_or_default()), None, ) .await; diff --git a/crates/cli/src/modules/domain.rs b/crates/cli/src/modules/domain.rs index f2fe73323..7152f27df 100644 --- a/crates/cli/src/modules/domain.rs +++ b/crates/cli/src/modules/domain.rs @@ -36,7 +36,7 @@ impl DomainCommands { client .http_request::( Method::POST, - &format!("/admin/domain/{name}"), + &format!("/api/domain/{name}"), None, ) .await; @@ -46,7 +46,7 @@ impl DomainCommands { client .http_request::( Method::DELETE, - &format!("/admin/domain/{name}"), + &format!("/api/domain/{name}"), None, ) .await; @@ -54,9 +54,9 @@ impl DomainCommands { } DomainCommands::List { from, limit } => { let query = if from.is_none() && limit.is_none() { - Cow::Borrowed("/admin/domain") + Cow::Borrowed("/api/domain") } else { - let mut query = "/admin/domain?".to_string(); + let mut query = "/api/domain?".to_string(); if let Some(from) = &from { query.push_str(&format!("from={from}")); } diff --git a/crates/cli/src/modules/group.rs b/crates/cli/src/modules/group.rs index 539ae373a..e872d931b 100644 --- a/crates/cli/src/modules/group.rs +++ b/crates/cli/src/modules/group.rs @@ -50,13 +50,13 @@ impl GroupCommands { ..Default::default() }; let account_id = client - .http_request::(Method::POST, "/admin/principal", Some(principal)) + .http_request::(Method::POST, "/api/principal", Some(principal)) .await; if let Some(members) = members { client .http_request::( Method::PATCH, - &format!("/admin/principal/{name}"), + &format!("/api/principal/{name}"), Some(vec![PrincipalUpdate::set( PrincipalField::Members, PrincipalValue::StringList(members), @@ -103,7 +103,7 @@ impl GroupCommands { client .http_request::( Method::PATCH, - &format!("/admin/principal/{name}"), + &format!("/api/principal/{name}"), Some(changes), ) .await; @@ -116,7 +116,7 @@ impl GroupCommands { client .http_request::( Method::PATCH, - &format!("/admin/principal/{name}"), + &format!("/api/principal/{name}"), Some( members .into_iter() @@ -136,7 +136,7 @@ impl GroupCommands { client .http_request::( Method::PATCH, - &format!("/admin/principal/{name}"), + &format!("/api/principal/{name}"), Some( members .into_iter() @@ -155,8 +155,14 @@ impl GroupCommands { GroupCommands::Display { name } => { client.display_principal(&name).await; } - GroupCommands::List { from, limit } => { - client.list_principals("group", "Group", from, limit).await; + GroupCommands::List { + filter, + limit, + page, + } => { + client + .list_principals("group", "Group", filter, page, limit) + .await; } } } diff --git a/crates/cli/src/modules/list.rs b/crates/cli/src/modules/list.rs index 88be27118..7ca475c95 100644 --- a/crates/cli/src/modules/list.rs +++ b/crates/cli/src/modules/list.rs @@ -50,13 +50,13 @@ impl ListCommands { ..Default::default() }; let account_id = client - .http_request::(Method::POST, "/admin/principal", Some(principal)) + .http_request::(Method::POST, "/api/principal", Some(principal)) .await; if let Some(members) = members { client .http_request::( Method::PATCH, - &format!("/admin/principal/{name}"), + &format!("/api/principal/{name}"), Some(vec![PrincipalUpdate::set( PrincipalField::Members, PrincipalValue::StringList(members), @@ -103,7 +103,7 @@ impl ListCommands { client .http_request::( Method::PATCH, - &format!("/admin/principal/{name}"), + &format!("/api/principal/{name}"), Some(changes), ) .await; @@ -116,7 +116,7 @@ impl ListCommands { client .http_request::( Method::PATCH, - &format!("/admin/principal/{name}"), + &format!("/api/principal/{name}"), Some( members .into_iter() @@ -136,7 +136,7 @@ impl ListCommands { client .http_request::( Method::PATCH, - &format!("/admin/principal/{name}"), + &format!("/api/principal/{name}"), Some( members .into_iter() @@ -155,9 +155,13 @@ impl ListCommands { ListCommands::Display { name } => { client.display_principal(&name).await; } - ListCommands::List { from, limit } => { + ListCommands::List { + filter, + limit, + page, + } => { client - .list_principals("list", "Mailing List", from, limit) + .list_principals("list", "Mailing List", filter, page, limit) .await; } } diff --git a/crates/cli/src/modules/queue.rs b/crates/cli/src/modules/queue.rs index 7b5dd5695..6c923e190 100644 --- a/crates/cli/src/modules/queue.rs +++ b/crates/cli/src/modules/queue.rs @@ -102,7 +102,7 @@ impl QueueCommands { for (message, id) in client .http_request::>, String>( Method::GET, - &build_query("/admin/queue/status?ids=", chunk), + &build_query("/api/queue/status?ids=", chunk), None, ) .await @@ -176,7 +176,7 @@ impl QueueCommands { for (message, id) in client .http_request::>, String>( Method::GET, - &build_query("/admin/queue/status?ids=", &parse_ids(&ids)), + &build_query("/api/queue/status?ids=", &parse_ids(&ids)), None, ) .await @@ -316,7 +316,7 @@ impl QueueCommands { std::process::exit(1); } - let mut query = form_urlencoded::Serializer::new("/admin/queue/retry?".to_string()); + let mut query = form_urlencoded::Serializer::new("/api/queue/retry?".to_string()); if let Some(filter) = &domain { query.append_pair("filter", filter); @@ -371,8 +371,7 @@ impl QueueCommands { std::process::exit(1); } - let mut query = - form_urlencoded::Serializer::new("/admin/queue/cancel?".to_string()); + let mut query = form_urlencoded::Serializer::new("/api/queue/cancel?".to_string()); if let Some(filter) = &rcpt { query.append_pair("filter", filter); @@ -414,7 +413,7 @@ impl Client { before: &Option, after: &Option, ) -> Vec { - let mut query = form_urlencoded::Serializer::new("/admin/queue/list?".to_string()); + let mut query = form_urlencoded::Serializer::new("/api/queue/list?".to_string()); if let Some(sender) = from { query.append_pair("from", sender); diff --git a/crates/cli/src/modules/report.rs b/crates/cli/src/modules/report.rs index 696f9054c..60f72d63f 100644 --- a/crates/cli/src/modules/report.rs +++ b/crates/cli/src/modules/report.rs @@ -51,7 +51,7 @@ impl ReportCommands { page_size, } => { let stdout = Term::buffered_stdout(); - let mut query = form_urlencoded::Serializer::new("/admin/report/list?".to_string()); + let mut query = form_urlencoded::Serializer::new("/api/report/list?".to_string()); if let Some(domain) = &domain { query.append_pair("domain", domain); @@ -78,7 +78,7 @@ impl ReportCommands { for (report, id) in client .http_request::>, String>( Method::GET, - &format!("/admin/report/status?ids={}", chunk.join(",")), + &format!("/api/report/status?ids={}", chunk.join(",")), None, ) .await @@ -117,7 +117,7 @@ impl ReportCommands { for (report, id) in client .http_request::>, String>( Method::GET, - &format!("/admin/report/status?ids={}", ids.join(",")), + &format!("/api/report/status?ids={}", ids.join(",")), None, ) .await @@ -173,7 +173,7 @@ impl ReportCommands { for (success, id) in client .http_request::, String>( Method::GET, - &format!("/admin/report/cancel?ids={}", ids.join(",")), + &format!("/api/report/cancel?ids={}", ids.join(",")), None, ) .await diff --git a/crates/directory/src/backend/internal/manage.rs b/crates/directory/src/backend/internal/manage.rs index ab664f0d8..ea881f10c 100644 --- a/crates/directory/src/backend/internal/manage.rs +++ b/crates/directory/src/backend/internal/manage.rs @@ -55,9 +55,8 @@ pub trait ManageDirectory: Sized { async fn delete_account(&self, by: QueryBy<'_>) -> crate::Result<()>; async fn list_accounts( &self, - start_from: Option<&str>, + filter: Option<&str>, typ: Option, - limit: usize, ) -> crate::Result>; async fn map_group_ids(&self, principal: Principal) -> crate::Result>; async fn map_group_names( @@ -67,11 +66,7 @@ pub trait ManageDirectory: Sized { ) -> crate::Result>; async fn create_domain(&self, domain: &str) -> crate::Result<()>; async fn delete_domain(&self, domain: &str) -> crate::Result<()>; - async fn list_domains( - &self, - start_from: Option<&str>, - limit: usize, - ) -> crate::Result>; + async fn list_domains(&self, filter: Option<&str>) -> crate::Result>; async fn init(self) -> crate::Result; } @@ -802,61 +797,88 @@ impl ManageDirectory for Store { async fn list_accounts( &self, - start_from: Option<&str>, + filter: Option<&str>, typ: Option, - limit: usize, ) -> crate::Result> { - let from_key = ValueKey::from(ValueClass::Directory(DirectoryClass::NameToId( - start_from.unwrap_or("").as_bytes().to_vec(), - ))); + let from_key = ValueKey::from(ValueClass::Directory(DirectoryClass::NameToId(vec![]))); let to_key = ValueKey::from(ValueClass::Directory(DirectoryClass::NameToId(vec![ u8::MAX; 10 ]))); - let mut results = Vec::with_capacity(limit); + let mut results = Vec::new(); self.iterate( - IterateParams::new(from_key, to_key) - .set_values(typ.is_some()) - .ascending(), + IterateParams::new(from_key, to_key).ascending(), |key, value| { - if typ.map_or(true, |t| { - PrincipalIdType::deserialize(value) - .map(|v| v.typ == t) - .unwrap_or(false) - }) { - results.push( + let pt = PrincipalIdType::deserialize(value)?; + + if typ.map_or(true, |t| pt.typ == t) { + results.push(( + pt.account_id, String::from_utf8_lossy(key.get(1..).unwrap_or_default()).into_owned(), - ); + )); } - Ok(limit == 0 || results.len() < limit) + + Ok(true) }, ) .await?; - Ok(results) + if let Some(filter) = filter { + let mut filtered = Vec::new(); + let filters = filter + .split_whitespace() + .map(|r| r.to_lowercase()) + .collect::>(); + + for (account_id, account_name) in results { + let principal = self + .get_value::>(ValueKey::from(ValueClass::Directory( + DirectoryClass::Principal(account_id), + ))) + .await? + .ok_or_else(|| { + DirectoryError::Management(ManagementError::NotFound( + account_id.to_string(), + )) + })?; + if filters.iter().all(|f| { + principal.name.to_lowercase().contains(f) + || principal + .description + .as_ref() + .map_or(false, |d| d.to_lowercase().contains(f)) + || principal + .emails + .iter() + .any(|email| email.to_lowercase().contains(f)) + }) { + filtered.push(account_name); + } + } + + Ok(filtered) + } else { + Ok(results.into_iter().map(|(_, name)| name).collect()) + } } - async fn list_domains( - &self, - start_from: Option<&str>, - limit: usize, - ) -> crate::Result> { - let from_key = ValueKey::from(ValueClass::Directory(DirectoryClass::Domain( - start_from.unwrap_or("").as_bytes().to_vec(), - ))); + async fn list_domains(&self, filter: Option<&str>) -> crate::Result> { + let from_key = ValueKey::from(ValueClass::Directory(DirectoryClass::Domain(vec![]))); let to_key = ValueKey::from(ValueClass::Directory(DirectoryClass::Domain(vec![ u8::MAX; 10 ]))); - let mut results = Vec::with_capacity(limit); + let mut results = Vec::new(); self.iterate( IterateParams::new(from_key, to_key).no_values().ascending(), |key, _| { - results - .push(String::from_utf8_lossy(key.get(1..).unwrap_or_default()).into_owned()); - Ok(limit == 0 || results.len() < limit) + let domain = String::from_utf8_lossy(key.get(1..).unwrap_or_default()).into_owned(); + if filter.map_or(true, |f| domain.contains(f)) { + results.push(domain); + } + Ok(true) }, ) .await?; diff --git a/crates/jmap/src/api/admin.rs b/crates/jmap/src/api/admin.rs index 06687a705..01e072d52 100644 --- a/crates/jmap/src/api/admin.rs +++ b/crates/jmap/src/api/admin.rs @@ -86,8 +86,9 @@ impl JMAP { } ("principal", None, &Method::GET) => { // List principal ids - let mut from_key = None; + let mut filter = None; let mut typ = None; + let mut page: usize = 0; let mut limit: usize = 0; if let Some(query) = req.uri().query() { @@ -96,26 +97,40 @@ impl JMAP { "limit" => { limit = value.parse().unwrap_or_default(); } + "page" => { + page = value.parse().unwrap_or_default(); + } "type" => { typ = Type::parse(value.as_ref()); } - "from" => { - from_key = value.into(); + "filter" => { + filter = value.into(); } _ => {} } } } - match self - .store - .list_accounts(from_key.as_deref(), typ, limit) - .await - { - Ok(accounts) => JsonResponse::new(json!({ - "data": accounts, - })) - .into_http_response(), + match self.store.list_accounts(filter.as_deref(), typ).await { + Ok(accounts) => { + let (total, accounts) = if limit > 0 { + let offset = page.saturating_sub(1) * limit; + ( + accounts.len(), + accounts.into_iter().skip(offset).take(limit).collect(), + ) + } else { + (accounts.len(), accounts) + }; + + JsonResponse::new(json!({ + "data": { + "items": accounts, + "total": total, + }, + })) + .into_http_response() + } Err(err) => map_directory_error(err), } } @@ -232,8 +247,9 @@ impl JMAP { } } ("domain", None, &Method::GET) => { - // List principal ids - let mut from_key = None; + // List domains + let mut filter = None; + let mut page: usize = 0; let mut limit: usize = 0; if let Some(query) = req.uri().query() { @@ -242,19 +258,37 @@ impl JMAP { "limit" => { limit = value.parse().unwrap_or_default(); } - "from" => { - from_key = value.into(); + "page" => { + page = value.parse().unwrap_or_default(); + } + "filter" => { + filter = value.into(); } _ => {} } } } - match self.store.list_domains(from_key.as_deref(), limit).await { - Ok(domains) => JsonResponse::new(json!({ - "data": domains, - })) - .into_http_response(), + match self.store.list_domains(filter.as_deref()).await { + Ok(domains) => { + let (total, domains) = if limit > 0 { + let offset = page.saturating_sub(1) * limit; + ( + domains.len(), + domains.into_iter().skip(offset).take(limit).collect(), + ) + } else { + (domains.len(), domains) + }; + + JsonResponse::new(json!({ + "data": { + "items": domains, + "total": total, + }, + })) + .into_http_response() + } Err(err) => map_directory_error(err), } } diff --git a/crates/jmap/src/api/http.rs b/crates/jmap/src/api/http.rs index 01550a635..7817fd1af 100644 --- a/crates/jmap/src/api/http.rs +++ b/crates/jmap/src/api/http.rs @@ -267,7 +267,7 @@ pub async fn parse_jmap_request( _ => (), } } - "admin" => { + "api" => { // Make sure the user is a superuser let body = match jmap.authenticate_headers(&req, remote_ip).await { Ok(Some((_, access_token))) if access_token.is_super_user() => { diff --git a/tests/resources/scripts/create_test_users.sh b/tests/resources/scripts/create_test_users.sh index 206000b4e..4d0094d53 100644 --- a/tests/resources/scripts/create_test_users.sh +++ b/tests/resources/scripts/create_test_users.sh @@ -1,19 +1,18 @@ #!/bin/bash -URL="https://127.0.0.1:443" -CREDENTIALS="admin:secret" +export URL="https://127.0.0.1:443" CREDENTIALS="admin:secret" cargo run -p stalwart-cli -- domain create example.org cargo run -p stalwart-cli -- account create john 12345 -d "John Doe" -a john@example.org -a john.doe@example.org -#cargo run -p stalwart-cli -- account create jane abcde -d "Jane Doe" -a jane@example.org -#cargo run -p stalwart-cli -- account create bill xyz12 -d "Bill Foobar" -a bill@example.org -#cargo run -p stalwart-cli -- group create sales -d "Sales Department" -#cargo run -p stalwart-cli -- group create support -d "Technical Support" -#cargo run -p stalwart-cli -- account add-to-group john sales support -#cargo run -p stalwart-cli -- account remove-from-group john support -#cargo run -p stalwart-cli -- account add-email jane jane.doe@example.org -#cargo run -p stalwart-cli -- list create everyone everyone@example.org -#cargo run -p stalwart-cli -- list add-members everyone jane john bill -#cargo run -p stalwart-cli -- account list -#cargo run -p stalwart-cli -- import messages --format mbox john _ignore/dovecot-crlf -#cargo run -p stalwart-cli -- import messages --format maildir john /var/mail/john +cargo run -p stalwart-cli -- account create jane abcde -d "Jane Doe" -a jane@example.org +cargo run -p stalwart-cli -- account create bill xyz12 -d "Bill Foobar" -a bill@example.org +cargo run -p stalwart-cli -- group create sales -d "Sales Department" +cargo run -p stalwart-cli -- group create support -d "Technical Support" +cargo run -p stalwart-cli -- account add-to-group john sales support +cargo run -p stalwart-cli -- account remove-from-group john support +cargo run -p stalwart-cli -- account add-email jane jane.doe@example.org +cargo run -p stalwart-cli -- list create everyone everyone@example.org +cargo run -p stalwart-cli -- list add-members everyone jane john bill +cargo run -p stalwart-cli -- account list +cargo run -p stalwart-cli -- import messages --format mbox john _ignore/dovecot-crlf +cargo run -p stalwart-cli -- import messages --format maildir john /var/mail/john diff --git a/tests/src/directory/internal.rs b/tests/src/directory/internal.rs index f3627cc3f..395cbabad 100644 --- a/tests/src/directory/internal.rs +++ b/tests/src/directory/internal.rs @@ -503,32 +503,26 @@ async fn internal_directory() { // List accounts assert_eq!( - store.list_accounts(None, None, 0).await.unwrap(), + store.list_accounts(None, None).await.unwrap(), vec!["jane", "john.doe", "list", "sales", "support"] ); assert_eq!( - store.list_accounts("john".into(), None, 2).await.unwrap(), - vec!["john.doe", "list"] + store.list_accounts("john".into(), None).await.unwrap(), + vec!["john.doe"] ); assert_eq!( store - .list_accounts(None, Type::Individual.into(), 0) + .list_accounts(None, Type::Individual.into()) .await .unwrap(), vec!["jane", "john.doe"] ); assert_eq!( - store - .list_accounts(None, Type::Group.into(), 0) - .await - .unwrap(), + store.list_accounts(None, Type::Group.into()).await.unwrap(), vec!["sales", "support"] ); assert_eq!( - store - .list_accounts(None, Type::List.into(), 0) - .await - .unwrap(), + store.list_accounts(None, Type::List.into()).await.unwrap(), vec!["list"] ); @@ -572,7 +566,7 @@ async fn internal_directory() { ); assert!(!store.rcpt("john.doe@example.org").await.unwrap()); assert_eq!( - store.list_accounts(None, None, 0).await.unwrap(), + store.list_accounts(None, None).await.unwrap(), vec!["jane", "list", "sales", "support"] ); assert_eq!(