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>, jar: CookieJar, ) -> AppResult>> { 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>, Path(key): Path, jar: CookieJar, headers: HeaderMap, Json(input): Json, ) -> AppResult> { 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>, Path(key): Path, jar: CookieJar, headers: HeaderMap, ) -> AppResult> { 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(()) }