v0.1.0
This commit is contained in:
1 parent
cdca612a59
commit
eb25f8de4a
56 files changed
+7848
-4345
No files matched your search
+103
-64
@@ -24,19 +24,98 @@ pub async fn overview(
|
||||
State(state): State<Arc<AppState>>,
|
||||
jar: CookieJar,
|
||||
) -> AppResult<Json<serde_json::Value>> {
|
||||
let s = routes::session(&state, &jar)?;
|
||||
let domains = count(&state, Table::Domain, "domain<>'ALL'").await?;
|
||||
let mailboxes = count_scoped(&state, Table::Mailbox, "domain", &s).await?;
|
||||
let aliases = count_scoped(&state, Table::Alias, "domain", &s).await?;
|
||||
Ok(Json(
|
||||
serde_json::json!({"domains":if s.role.is_global(){domains}else{s.allowed_domains.iter().filter(|d|d.as_str()!="ALL").count() as i64},"mailboxes":mailboxes,"aliases":aliases,"role":s.role,"username":s.username,"features":&state.config.features}),
|
||||
))
|
||||
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)?;
|
||||
let s = routes::session(&state, &jar).await?;
|
||||
s.require_global()?;
|
||||
let warnings = state.db.check_schema(&state.config).await?;
|
||||
Ok(Json(
|
||||
@@ -47,62 +126,22 @@ pub async fn features(
|
||||
State(state): State<Arc<AppState>>,
|
||||
jar: CookieJar,
|
||||
) -> AppResult<Json<serde_json::Value>> {
|
||||
let _ = routes::session(&state, &jar)?;
|
||||
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!({"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},"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")}),
|
||||
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")}),
|
||||
))
|
||||
}
|
||||
async fn count(state: &AppState, table: Table, where_sql: &str) -> AppResult<i64> {
|
||||
let t = state.db.table(table);
|
||||
let r = sqlx::query(&format!("SELECT COUNT(*) AS n FROM {t} WHERE {where_sql}"))
|
||||
.fetch_one(state.db.pool())
|
||||
.await?;
|
||||
Ok(row_i64(&r, "n"))
|
||||
}
|
||||
async fn count_scoped(
|
||||
state: &AppState,
|
||||
table: Table,
|
||||
col: &str,
|
||||
s: &crate::auth::Session,
|
||||
) -> AppResult<i64> {
|
||||
let t = state.db.table(table);
|
||||
if s.role.is_global() {
|
||||
let r = sqlx::query(&format!("SELECT COUNT(*) AS n FROM {t}"))
|
||||
.fetch_one(state.db.pool())
|
||||
.await?;
|
||||
return Ok(row_i64(&r, "n"));
|
||||
}
|
||||
if !s.role.is_admin() {
|
||||
let q = state.db.q(&format!(
|
||||
"SELECT COUNT(*) AS n FROM {t} WHERE {}=?",
|
||||
if matches!(table, Table::Mailbox) {
|
||||
"username"
|
||||
} else {
|
||||
"address"
|
||||
}
|
||||
));
|
||||
let r = sqlx::query(&q)
|
||||
.bind(&s.username)
|
||||
.fetch_one(state.db.pool())
|
||||
.await?;
|
||||
return Ok(row_i64(&r, "n"));
|
||||
}
|
||||
let ds: Vec<_> = s
|
||||
.allowed_domains
|
||||
.iter()
|
||||
.filter(|d| d.as_str() != "ALL")
|
||||
.collect();
|
||||
if ds.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
let q = state.db.q(&format!(
|
||||
"SELECT COUNT(*) AS n FROM {t} WHERE {col} IN ({})",
|
||||
vec!["?"; ds.len()].join(",")
|
||||
));
|
||||
let mut x = sqlx::query(&q);
|
||||
for d in ds {
|
||||
x = x.bind(d.as_str());
|
||||
}
|
||||
let r = x.fetch_one(state.db.pool()).await?;
|
||||
Ok(row_i64(&r, "n"))
|
||||
}
|
||||
Reference in new issue
Block a user