first commit
This commit is contained in:
commit
2eb8aac027
91 files changed
+20139
No files matched your search
@@ -0,0 +1,156 @@
|
||||
use crate::{
|
||||
db::{Table, row_bool, row_i64, row_string},
|
||||
error::{AppError, AppResult},
|
||||
models::{AccountKind, VacationInput, VacationSummary},
|
||||
routes,
|
||||
state::AppState,
|
||||
validation,
|
||||
};
|
||||
use axum::{
|
||||
Json,
|
||||
extract::{Path, State},
|
||||
http::HeaderMap,
|
||||
};
|
||||
use axum_extra::extract::cookie::CookieJar;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub async fn get(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(path): Path<String>,
|
||||
jar: CookieJar,
|
||||
) -> AppResult<Json<Option<VacationSummary>>> {
|
||||
let session = routes::session(&state, &jar)?;
|
||||
let u = validation::email(&path)?;
|
||||
authorize(&state, &session, &u, false)?;
|
||||
if !state.config.features.vacation || !state.db.has_table(Table::Vacation).await {
|
||||
return Ok(Json(None));
|
||||
}
|
||||
let t = state.db.table(Table::Vacation);
|
||||
let af = state.db.cast_text("activefrom");
|
||||
let au = state.db.cast_text("activeuntil");
|
||||
let active = state.db.flag("active", "active");
|
||||
let q=state.db.q(&format!("SELECT email,subject,body,interval_time,{af} AS activefrom,{au} AS activeuntil,{active} FROM {t} WHERE email=?"));
|
||||
let row = sqlx::query(&q)
|
||||
.bind(&u)
|
||||
.fetch_optional(state.db.pool())
|
||||
.await?;
|
||||
Ok(Json(row.as_ref().map(map)))
|
||||
}
|
||||
pub async fn set(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(path): Path<String>,
|
||||
jar: CookieJar,
|
||||
headers: HeaderMap,
|
||||
Json(input): Json<VacationInput>,
|
||||
) -> AppResult<Json<serde_json::Value>> {
|
||||
let session = routes::mutation_session(&state, &jar, &headers)?;
|
||||
let u = validation::email(&path)?;
|
||||
authorize(&state, &session, &u, true)?;
|
||||
if validation::email(&input.email)? != u {
|
||||
return Err(AppError::bad_request(
|
||||
"Vacation email does not match mailbox",
|
||||
));
|
||||
}
|
||||
if input.subject.len() > 255 || input.body.len() > 65535 {
|
||||
return Err(AppError::bad_request("Vacation message is too long"));
|
||||
}
|
||||
let (_, domain) = u.rsplit_once('@').unwrap();
|
||||
let t = state.db.table(Table::Vacation);
|
||||
let check = state.db.q(&format!("SELECT 1 FROM {t} WHERE email=?"));
|
||||
let exists = sqlx::query(&check)
|
||||
.bind(&u)
|
||||
.fetch_optional(state.db.pool())
|
||||
.await?
|
||||
.is_some();
|
||||
let q = if exists {
|
||||
state.db.q(&format!("UPDATE {t} SET subject=?,body=?,activefrom=?,activeuntil=?,interval_time=?,modified=CURRENT_TIMESTAMP,active=? WHERE email=?"))
|
||||
} else {
|
||||
state.db.q(&format!("INSERT INTO {t} (email,subject,body,activefrom,activeuntil,cache,domain,interval_time,created,modified,active) VALUES (?,?,?,?,?,'',?,?,CURRENT_TIMESTAMP,CURRENT_TIMESTAMP,?)"))
|
||||
};
|
||||
if exists {
|
||||
sqlx::query(&q)
|
||||
.bind(&input.subject)
|
||||
.bind(&input.body)
|
||||
.bind(&input.active_from)
|
||||
.bind(&input.active_until)
|
||||
.bind(input.interval_time)
|
||||
.bind(input.active)
|
||||
.bind(&u)
|
||||
.execute(state.db.pool())
|
||||
.await?;
|
||||
} else {
|
||||
sqlx::query(&q)
|
||||
.bind(&u)
|
||||
.bind(&input.subject)
|
||||
.bind(&input.body)
|
||||
.bind(&input.active_from)
|
||||
.bind(&input.active_until)
|
||||
.bind(domain)
|
||||
.bind(input.interval_time)
|
||||
.bind(input.active)
|
||||
.execute(state.db.pool())
|
||||
.await?;
|
||||
}
|
||||
state
|
||||
.db
|
||||
.log(
|
||||
&state.config,
|
||||
&session.username,
|
||||
domain,
|
||||
"edit_vacation",
|
||||
&u,
|
||||
)
|
||||
.await;
|
||||
Ok(Json(serde_json::json!({"ok":true})))
|
||||
}
|
||||
pub async fn remove(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(path): Path<String>,
|
||||
jar: CookieJar,
|
||||
headers: HeaderMap,
|
||||
) -> AppResult<Json<serde_json::Value>> {
|
||||
let session = routes::mutation_session(&state, &jar, &headers)?;
|
||||
let u = validation::email(&path)?;
|
||||
authorize(&state, &session, &u, true)?;
|
||||
let t = state.db.table(Table::Vacation);
|
||||
let q = state.db.q(&format!("DELETE FROM {t} WHERE email=?"));
|
||||
sqlx::query(&q).bind(&u).execute(state.db.pool()).await?;
|
||||
if state.db.has_table(Table::VacationNotification).await {
|
||||
let n = state.db.table(Table::VacationNotification);
|
||||
let q = state.db.q(&format!("DELETE FROM {n} WHERE on_vacation=?"));
|
||||
let _ = sqlx::query(&q).bind(&u).execute(state.db.pool()).await;
|
||||
}
|
||||
Ok(Json(serde_json::json!({"ok":true})))
|
||||
}
|
||||
fn authorize(state: &AppState, s: &crate::auth::Session, u: &str, write: bool) -> AppResult<()> {
|
||||
if !state.config.features.vacation {
|
||||
return Err(AppError::forbidden("Vacation is disabled"));
|
||||
}
|
||||
if s.kind == AccountKind::User {
|
||||
if !state.config.features.vacation_control || s.username != u {
|
||||
return Err(AppError::forbidden(
|
||||
"Vacation control is disabled for this user",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
if write && !state.config.features.vacation_control_admin && !s.role.is_global() {
|
||||
return Err(AppError::forbidden(
|
||||
"Vacation control is disabled for domain administrators",
|
||||
));
|
||||
}
|
||||
let (_, d) = u.rsplit_once('@').unwrap();
|
||||
s.require_domain(d)
|
||||
}
|
||||
}
|
||||
fn map(r: &sqlx::any::AnyRow) -> VacationSummary {
|
||||
VacationSummary {
|
||||
email: row_string(r, "email"),
|
||||
subject: row_string(r, "subject"),
|
||||
body: row_string(r, "body"),
|
||||
interval_time: row_i64(r, "interval_time"),
|
||||
active_from: row_string(r, "activefrom"),
|
||||
active_until: row_string(r, "activeuntil"),
|
||||
active: row_bool(r, "active"),
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user