mirror of
https://github.com/stalwartlabs/mail-server.git
synced 2024-09-20 07:16:18 +08:00
Updated REST principal API
This commit is contained in:
parent
afe10e6d81
commit
8027f135bc
|
@ -61,7 +61,7 @@ impl AccountCommands {
|
|||
..Default::default()
|
||||
};
|
||||
let account_id = client
|
||||
.http_request::<u32, _>(Method::POST, "/admin/principal", Some(principal))
|
||||
.http_request::<u32, _>(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::<Value, _>(
|
||||
Method::PATCH,
|
||||
&format!("/admin/principal/{name}"),
|
||||
&format!("/api/principal/{name}"),
|
||||
Some(changes),
|
||||
)
|
||||
.await;
|
||||
|
@ -144,7 +144,7 @@ impl AccountCommands {
|
|||
client
|
||||
.http_request::<Value, _>(
|
||||
Method::PATCH,
|
||||
&format!("/admin/principal/{name}"),
|
||||
&format!("/api/principal/{name}"),
|
||||
Some(
|
||||
addresses
|
||||
.into_iter()
|
||||
|
@ -164,7 +164,7 @@ impl AccountCommands {
|
|||
client
|
||||
.http_request::<Value, _>(
|
||||
Method::PATCH,
|
||||
&format!("/admin/principal/{name}"),
|
||||
&format!("/api/principal/{name}"),
|
||||
Some(
|
||||
addresses
|
||||
.into_iter()
|
||||
|
@ -184,7 +184,7 @@ impl AccountCommands {
|
|||
client
|
||||
.http_request::<Value, _>(
|
||||
Method::PATCH,
|
||||
&format!("/admin/principal/{name}"),
|
||||
&format!("/api/principal/{name}"),
|
||||
Some(
|
||||
member_of
|
||||
.into_iter()
|
||||
|
@ -204,7 +204,7 @@ impl AccountCommands {
|
|||
client
|
||||
.http_request::<Value, _>(
|
||||
Method::PATCH,
|
||||
&format!("/admin/principal/{name}"),
|
||||
&format!("/api/principal/{name}"),
|
||||
Some(
|
||||
member_of
|
||||
.into_iter()
|
||||
|
@ -224,7 +224,7 @@ impl AccountCommands {
|
|||
client
|
||||
.http_request::<Value, String>(
|
||||
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::<Principal, String>(
|
||||
Method::GET,
|
||||
&format!("/admin/principal/{name}"),
|
||||
None,
|
||||
)
|
||||
.http_request::<Principal, String>(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<String>,
|
||||
filter: Option<String>,
|
||||
page: Option<usize>,
|
||||
limit: Option<usize>,
|
||||
) {
|
||||
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::<Vec<String>, String>(Method::GET, &query.finish(), None)
|
||||
.http_request::<ListResponse, String>(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<String>,
|
||||
}
|
||||
|
||||
impl Display for Type {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
|
|
|
@ -190,10 +190,12 @@ pub enum AccountCommands {
|
|||
|
||||
/// List all user accounts
|
||||
List {
|
||||
/// Starting point for listing accounts
|
||||
from: Option<String>,
|
||||
/// Filter accounts by keywords
|
||||
filter: Option<String>,
|
||||
/// Maximum number of accounts to list
|
||||
limit: Option<usize>,
|
||||
/// Page number
|
||||
page: Option<usize>,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -255,10 +257,12 @@ pub enum ListCommands {
|
|||
|
||||
/// List all mailing lists
|
||||
List {
|
||||
/// Starting point for listing mailing lists
|
||||
from: Option<String>,
|
||||
/// Filter mailing lists by keywords
|
||||
filter: Option<String>,
|
||||
/// Maximum number of mailing lists to list
|
||||
limit: Option<usize>,
|
||||
/// Page number
|
||||
page: Option<usize>,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -320,10 +324,12 @@ pub enum GroupCommands {
|
|||
|
||||
/// List all groups
|
||||
List {
|
||||
/// Starting point for listing groups
|
||||
from: Option<String>,
|
||||
/// Filter groups by keywords
|
||||
filter: Option<String>,
|
||||
/// Maximum number of groups to list
|
||||
limit: Option<usize>,
|
||||
/// Page number
|
||||
page: Option<usize>,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -32,19 +32,19 @@ impl ServerCommands {
|
|||
match self {
|
||||
ServerCommands::DatabaseMaintenance {} => {
|
||||
client
|
||||
.http_request::<Value, String>(Method::GET, "/admin/store/maintenance", None)
|
||||
.http_request::<Value, String>(Method::GET, "/api/store/maintenance", None)
|
||||
.await;
|
||||
eprintln!("Success.");
|
||||
}
|
||||
ServerCommands::ReloadCertificates {} => {
|
||||
client
|
||||
.http_request::<Value, String>(Method::GET, "/admin/reload/certificates", None)
|
||||
.http_request::<Value, String>(Method::GET, "/api/reload/certificates", None)
|
||||
.await;
|
||||
eprintln!("Success.");
|
||||
}
|
||||
ServerCommands::ReloadConfig {} => {
|
||||
client
|
||||
.http_request::<Value, String>(Method::GET, "/admin/reload/config", None)
|
||||
.http_request::<Value, String>(Method::GET, "/api/reload/config", None)
|
||||
.await;
|
||||
eprintln!("Success.");
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ impl ServerCommands {
|
|||
client
|
||||
.http_request::<Value, _>(
|
||||
Method::POST,
|
||||
"/admin/config",
|
||||
"/api/config",
|
||||
Some(vec![(key.clone(), value.unwrap_or_default())]),
|
||||
)
|
||||
.await;
|
||||
|
@ -62,7 +62,7 @@ impl ServerCommands {
|
|||
client
|
||||
.http_request::<Value, String>(
|
||||
Method::DELETE,
|
||||
&format!("/admin/config/{key}"),
|
||||
&format!("/api/config/{key}"),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
@ -72,7 +72,7 @@ impl ServerCommands {
|
|||
let results = client
|
||||
.http_request::<Vec<(String, String)>, String>(
|
||||
Method::GET,
|
||||
&format!("/admin/config/{}", prefix.unwrap_or_default()),
|
||||
&format!("/api/config/{}", prefix.unwrap_or_default()),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
|
|
@ -36,7 +36,7 @@ impl DomainCommands {
|
|||
client
|
||||
.http_request::<Value, String>(
|
||||
Method::POST,
|
||||
&format!("/admin/domain/{name}"),
|
||||
&format!("/api/domain/{name}"),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
@ -46,7 +46,7 @@ impl DomainCommands {
|
|||
client
|
||||
.http_request::<Value, String>(
|
||||
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}"));
|
||||
}
|
||||
|
|
|
@ -50,13 +50,13 @@ impl GroupCommands {
|
|||
..Default::default()
|
||||
};
|
||||
let account_id = client
|
||||
.http_request::<u32, _>(Method::POST, "/admin/principal", Some(principal))
|
||||
.http_request::<u32, _>(Method::POST, "/api/principal", Some(principal))
|
||||
.await;
|
||||
if let Some(members) = members {
|
||||
client
|
||||
.http_request::<Value, _>(
|
||||
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::<Value, _>(
|
||||
Method::PATCH,
|
||||
&format!("/admin/principal/{name}"),
|
||||
&format!("/api/principal/{name}"),
|
||||
Some(changes),
|
||||
)
|
||||
.await;
|
||||
|
@ -116,7 +116,7 @@ impl GroupCommands {
|
|||
client
|
||||
.http_request::<Value, _>(
|
||||
Method::PATCH,
|
||||
&format!("/admin/principal/{name}"),
|
||||
&format!("/api/principal/{name}"),
|
||||
Some(
|
||||
members
|
||||
.into_iter()
|
||||
|
@ -136,7 +136,7 @@ impl GroupCommands {
|
|||
client
|
||||
.http_request::<Value, _>(
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,13 +50,13 @@ impl ListCommands {
|
|||
..Default::default()
|
||||
};
|
||||
let account_id = client
|
||||
.http_request::<u32, _>(Method::POST, "/admin/principal", Some(principal))
|
||||
.http_request::<u32, _>(Method::POST, "/api/principal", Some(principal))
|
||||
.await;
|
||||
if let Some(members) = members {
|
||||
client
|
||||
.http_request::<Value, _>(
|
||||
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::<Value, _>(
|
||||
Method::PATCH,
|
||||
&format!("/admin/principal/{name}"),
|
||||
&format!("/api/principal/{name}"),
|
||||
Some(changes),
|
||||
)
|
||||
.await;
|
||||
|
@ -116,7 +116,7 @@ impl ListCommands {
|
|||
client
|
||||
.http_request::<Value, _>(
|
||||
Method::PATCH,
|
||||
&format!("/admin/principal/{name}"),
|
||||
&format!("/api/principal/{name}"),
|
||||
Some(
|
||||
members
|
||||
.into_iter()
|
||||
|
@ -136,7 +136,7 @@ impl ListCommands {
|
|||
client
|
||||
.http_request::<Value, _>(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@ impl QueueCommands {
|
|||
for (message, id) in client
|
||||
.http_request::<Vec<Option<Message>>, 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::<Vec<Option<Message>>, 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<DateTime>,
|
||||
after: &Option<DateTime>,
|
||||
) -> Vec<u64> {
|
||||
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);
|
||||
|
|
|
@ -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::<Vec<Option<Report>>, 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::<Vec<Option<Report>>, 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::<Vec<bool>, String>(
|
||||
Method::GET,
|
||||
&format!("/admin/report/cancel?ids={}", ids.join(",")),
|
||||
&format!("/api/report/cancel?ids={}", ids.join(",")),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
|
|
|
@ -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<Type>,
|
||||
limit: usize,
|
||||
) -> crate::Result<Vec<String>>;
|
||||
async fn map_group_ids(&self, principal: Principal<u32>) -> crate::Result<Principal<String>>;
|
||||
async fn map_group_names(
|
||||
|
@ -67,11 +66,7 @@ pub trait ManageDirectory: Sized {
|
|||
) -> crate::Result<Principal<u32>>;
|
||||
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<Vec<String>>;
|
||||
async fn list_domains(&self, filter: Option<&str>) -> crate::Result<Vec<String>>;
|
||||
async fn init(self) -> crate::Result<Self>;
|
||||
}
|
||||
|
||||
|
@ -802,61 +797,88 @@ impl ManageDirectory for Store {
|
|||
|
||||
async fn list_accounts(
|
||||
&self,
|
||||
start_from: Option<&str>,
|
||||
filter: Option<&str>,
|
||||
typ: Option<Type>,
|
||||
limit: usize,
|
||||
) -> crate::Result<Vec<String>> {
|
||||
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::<Vec<_>>();
|
||||
|
||||
for (account_id, account_name) in results {
|
||||
let principal = self
|
||||
.get_value::<Principal<u32>>(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);
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_domains(
|
||||
&self,
|
||||
start_from: Option<&str>,
|
||||
limit: usize,
|
||||
) -> crate::Result<Vec<String>> {
|
||||
let from_key = ValueKey::from(ValueClass::Directory(DirectoryClass::Domain(
|
||||
start_from.unwrap_or("").as_bytes().to_vec(),
|
||||
)));
|
||||
Ok(filtered)
|
||||
} else {
|
||||
Ok(results.into_iter().map(|(_, name)| name).collect())
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_domains(&self, filter: Option<&str>) -> crate::Result<Vec<String>> {
|
||||
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?;
|
||||
|
|
|
@ -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,
|
||||
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(),
|
||||
.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,
|
||||
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(),
|
||||
.into_http_response()
|
||||
}
|
||||
Err(err) => map_directory_error(err),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() => {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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!(
|
||||
|
|
Loading…
Reference in a new issue