Files
PostfixAdminRS/src/routes/preferences.rs
T
2026-11-03 16:52:28 +01:00

107 lines
2.8 KiB
Rust

use std::sync::Arc;
use axum::{
Json,
extract::{Path, State},
http::HeaderMap,
};
use axum_extra::extract::cookie::CookieJar;
use serde::{Deserialize, Serialize};
use crate::{
db::{Table, row_string},
error::{AppError, AppResult},
routes,
state::AppState,
};
#[derive(Debug, Serialize)]
pub struct Preference {
pub key: String,
pub value: String,
}
#[derive(Debug, Deserialize)]
pub struct PreferenceInput {
pub value: String,
}
pub async fn list(
State(state): State<Arc<AppState>>,
jar: CookieJar,
) -> AppResult<Json<Vec<Preference>>> {
let session = routes::session(&state, &jar)?;
session.require_admin()?;
if !state.db.has_table(Table::AdminPreferences).await {
return Ok(Json(Vec::new()));
}
let table = state.db.table(Table::AdminPreferences);
let sql = state.db.q(&format!(
"SELECT pref_key,pref_value FROM {table} WHERE username=? ORDER BY pref_key"
));
let rows = sqlx::query(&sql)
.bind(&session.username)
.fetch_all(state.db.pool())
.await?;
Ok(Json(
rows.iter()
.map(|row| Preference {
key: row_string(row, "pref_key"),
value: row_string(row, "pref_value"),
})
.collect(),
))
}
pub async fn set(
State(state): State<Arc<AppState>>,
Path(key): Path<String>,
jar: CookieJar,
headers: HeaderMap,
Json(input): Json<PreferenceInput>,
) -> AppResult<Json<serde_json::Value>> {
let session = routes::mutation_session(&state, &jar, &headers)?;
session.require_admin()?;
validate_key(&key)?;
if input.value.len() > 2048 {
return Err(AppError::bad_request("Preference value is too long"));
}
if !state.db.has_table(Table::AdminPreferences).await {
return Err(AppError::conflict(
"admin_preferences table is not available",
));
}
state
.db
.set_admin_pref(&session.username, &key, &input.value)
.await?;
Ok(Json(serde_json::json!({"ok": true})))
}
pub async fn remove(
State(state): State<Arc<AppState>>,
Path(key): Path<String>,
jar: CookieJar,
headers: HeaderMap,
) -> AppResult<Json<serde_json::Value>> {
let session = routes::mutation_session(&state, &jar, &headers)?;
session.require_admin()?;
validate_key(&key)?;
if state.db.has_table(Table::AdminPreferences).await {
state.db.delete_admin_pref(&session.username, &key).await?;
}
Ok(Json(serde_json::json!({"ok": true})))
}
fn validate_key(key: &str) -> AppResult<()> {
if key.is_empty()
|| key.len() > 64
|| !key
.bytes()
.all(|b| b.is_ascii_alphanumeric() || matches!(b, b'_' | b'-' | b'.'))
{
return Err(AppError::bad_request("Invalid preference key"));
}
Ok(())
}