148 lines
6.7 KiB
Rust
148 lines
6.7 KiB
Rust
use crate::{
|
|
db::{Table, row_i64},
|
|
error::AppResult,
|
|
passwords, routes,
|
|
state::AppState,
|
|
};
|
|
use axum::{Json, extract::State};
|
|
use axum_extra::extract::cookie::CookieJar;
|
|
use serde::Serialize;
|
|
use std::sync::Arc;
|
|
#[derive(Serialize)]
|
|
pub struct Health {
|
|
status: &'static str,
|
|
version: &'static str,
|
|
}
|
|
pub async fn health(State(state): State<Arc<AppState>>) -> AppResult<Json<Health>> {
|
|
state.db.ping().await?;
|
|
Ok(Json(Health {
|
|
status: "ok",
|
|
version: env!("CARGO_PKG_VERSION"),
|
|
}))
|
|
}
|
|
pub async fn overview(
|
|
State(state): State<Arc<AppState>>,
|
|
jar: CookieJar,
|
|
) -> AppResult<Json<serde_json::Value>> {
|
|
let s = routes::session(&state, &jar).await?;
|
|
if !s.role.is_admin() {
|
|
return Ok(Json(serde_json::json!({
|
|
"role": s.role,
|
|
"username": s.username,
|
|
})));
|
|
}
|
|
let scope: Option<Vec<String>> = if s.role.is_global() {
|
|
None
|
|
} else {
|
|
Some(routes::scoped_domains(&s))
|
|
};
|
|
let (in_clause, binds) = match &scope {
|
|
Some(domains) if domains.is_empty() => {
|
|
return Ok(Json(serde_json::json!({
|
|
"domains": 0, "mailboxes": 0, "aliases": 0, "alias_domains": 0,
|
|
"role": s.role, "username": s.username,
|
|
})));
|
|
}
|
|
Some(domains) => (
|
|
format!(" AND {{col}} IN ({})", vec!["?"; domains.len()].join(",")),
|
|
domains.clone(),
|
|
),
|
|
None => (String::new(), Vec::new()),
|
|
};
|
|
let db = &state.db;
|
|
let domain = db.table(Table::Domain);
|
|
let mailbox = db.table(Table::Mailbox);
|
|
let alias = db.table(Table::Alias);
|
|
let mut counts = serde_json::Map::new();
|
|
let queries = vec![
|
|
(
|
|
"domains",
|
|
format!(
|
|
"SELECT COUNT(*) AS n FROM {domain} d WHERE d.domain <> 'ALL'{}",
|
|
in_clause.replace("{col}", "d.domain")
|
|
),
|
|
),
|
|
(
|
|
"mailboxes",
|
|
format!(
|
|
"SELECT COUNT(*) AS n FROM {mailbox} m WHERE 1=1{}",
|
|
in_clause.replace("{col}", "m.domain")
|
|
),
|
|
),
|
|
(
|
|
"aliases",
|
|
format!(
|
|
"SELECT COUNT(*) AS n FROM {alias} a LEFT JOIN {mailbox} m ON m.username=a.address WHERE m.username IS NULL{}",
|
|
in_clause.replace("{col}", "a.domain")
|
|
),
|
|
),
|
|
];
|
|
for (key, sql) in queries {
|
|
let sql = db.q(&sql);
|
|
let mut q = sqlx::query(&sql);
|
|
for b in &binds {
|
|
q = q.bind(b.as_str());
|
|
}
|
|
counts.insert(
|
|
key.into(),
|
|
row_i64(&q.fetch_one(db.pool()).await?, "n").into(),
|
|
);
|
|
}
|
|
let alias_domains =
|
|
if state.config.features.alias_domain && db.has_table(Table::AliasDomain).await {
|
|
let ad = db.table(Table::AliasDomain);
|
|
let sql = db.q(&format!(
|
|
"SELECT COUNT(*) AS n FROM {ad} x WHERE 1=1{}",
|
|
in_clause.replace("{col}", "x.target_domain")
|
|
));
|
|
let mut q = sqlx::query(&sql);
|
|
for b in &binds {
|
|
q = q.bind(b.as_str());
|
|
}
|
|
row_i64(&q.fetch_one(db.pool()).await?, "n")
|
|
} else {
|
|
0
|
|
};
|
|
counts.insert("alias_domains".into(), alias_domains.into());
|
|
counts.insert(
|
|
"role".into(),
|
|
serde_json::to_value(s.role).unwrap_or_default(),
|
|
);
|
|
counts.insert("username".into(), s.username.clone().into());
|
|
Ok(Json(serde_json::Value::Object(counts)))
|
|
}
|
|
pub async fn schema(
|
|
State(state): State<Arc<AppState>>,
|
|
jar: CookieJar,
|
|
) -> AppResult<Json<serde_json::Value>> {
|
|
let s = routes::session(&state, &jar).await?;
|
|
s.require_global()?;
|
|
let warnings = state.db.check_schema(&state.config).await?;
|
|
Ok(Json(
|
|
serde_json::json!({"ok":warnings.is_empty(),"warnings":warnings,"database_backend":state.config.database.backend}),
|
|
))
|
|
}
|
|
pub async fn features(
|
|
State(state): State<Arc<AppState>>,
|
|
jar: CookieJar,
|
|
) -> AppResult<Json<serde_json::Value>> {
|
|
let s = routes::session(&state, &jar).await?;
|
|
let f = &state.config.features;
|
|
let domain = s.username.rsplit_once('@').map(|(_, d)| d).unwrap_or("");
|
|
let permissions = serde_json::json!({
|
|
"edit_own_name": s.kind == crate::models::AccountKind::User && f.edit_mailbox_name.allows(domain),
|
|
"edit_own_forwarding": s.kind == crate::models::AccountKind::User && f.edit_alias,
|
|
"edit_own_vacation": s.kind == crate::models::AccountKind::User && f.vacation && f.vacation_control,
|
|
"edit_mailbox_forwarding": s.role.is_admin() && if s.role.is_global() { f.alias_control } else { f.alias_control_admin },
|
|
"edit_vacation": s.role.is_admin() && f.vacation && (s.role.is_global() || f.vacation_control_admin),
|
|
"manage_domains": s.role.is_global(),
|
|
"manage_admins": s.role.is_global(),
|
|
"edit_special_aliases": s.role.is_global() || f.special_alias_control,
|
|
"sendmail": s.role.is_admin() && f.sendmail && (s.role.is_global() || f.sendmail_all_admins),
|
|
"backup": s.role.is_global() && f.backup,
|
|
});
|
|
Ok(Json(
|
|
serde_json::json!({"permissions":permissions,"features":&state.config.features,"defaults":{"page_size":state.config.defaults.page_size,"quota_multiplier":state.config.defaults.quota_multiplier,"quota_level_med_pct":state.config.defaults.quota_level_med_pct,"quota_level_high_pct":state.config.defaults.quota_level_high_pct,"motd_user":state.config.defaults.motd_user,"motd_admin":state.config.defaults.motd_admin,"motd_superadmin":state.config.defaults.motd_superadmin,"vacation_choice_of_reply":state.config.defaults.vacation_choice_of_reply,"transport_options":state.config.defaults.transport_options,"transport_default":state.config.defaults.transport_default,"default_aliases":state.config.defaults.default_aliases.keys().collect::<Vec<_>>(),"password_min_length":state.config.password.min_length,"password_min_letters":state.config.password.min_letters,"password_min_digits":state.config.password.min_digits,"mailbox_postpassword_hook":!state.config.hooks.mailbox_postpassword.is_empty()},"password":{"configured":state.config.password.scheme,"supported":passwords::supported_schemes()},"development_mock":state.config.database.dev_mock,"database_read_only":state.config.database.read_only,"dkim":{"default_backend":state.config.dkim.default_backend,"user_read_only":state.config.dkim.user_read_only},"rspamd_dkim":{"enabled":state.config.rspamd_dkim.enabled,"default_selector":state.config.rspamd_dkim.default_selector,"development_mock":state.config.rspamd_dkim.dev_mock},"sieve":{"enabled":state.config.sieve.enabled,"port":state.config.sieve.port,"tls_mode":state.config.sieve.tls_mode,"read_only":state.config.sieve.read_only},"version":env!("CARGO_PKG_VERSION")}),
|
|
))
|
|
}
|