init
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
use axum::extract::State;
|
||||
use axum::routing::{get, patch};
|
||||
use axum::{Json, Router};
|
||||
use serde::Deserialize;
|
||||
use serde_json::{json, Value};
|
||||
use validator::Validate;
|
||||
|
||||
use crate::auth::session::AuthUser;
|
||||
use crate::error::{AppError, AppResult};
|
||||
use crate::models::UserSettings;
|
||||
use crate::state::AppState;
|
||||
|
||||
pub fn router() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/health", get(health))
|
||||
.route("/settings", patch(update_settings))
|
||||
.route("/profile", patch(update_profile))
|
||||
}
|
||||
|
||||
async fn health() -> Json<Value> {
|
||||
Json(json!({ "status": "ok" }))
|
||||
}
|
||||
|
||||
const ALLOWED_LOCALES: &[&str] = &["de", "en"];
|
||||
const ALLOWED_THEMES: &[&str] = &["breakcore", "grunge", "minimal"];
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SettingsReq {
|
||||
locale: Option<String>,
|
||||
#[serde(default)]
|
||||
currency: Option<String>,
|
||||
theme: Option<String>,
|
||||
notify_email: Option<bool>,
|
||||
}
|
||||
|
||||
async fn update_settings(
|
||||
State(state): State<AppState>,
|
||||
AuthUser(user): AuthUser,
|
||||
Json(req): Json<SettingsReq>,
|
||||
) -> AppResult<Json<UserSettings>> {
|
||||
if let Some(loc) = &req.locale {
|
||||
if !ALLOWED_LOCALES.contains(&loc.as_str()) {
|
||||
return Err(AppError::Validation(format!("unsupported locale: {loc}")));
|
||||
}
|
||||
}
|
||||
if let Some(theme) = &req.theme {
|
||||
if !ALLOWED_THEMES.contains(&theme.as_str()) {
|
||||
return Err(AppError::Validation(format!("unsupported theme: {theme}")));
|
||||
}
|
||||
}
|
||||
if let Some(cur) = &req.currency {
|
||||
if cur.len() != 3 {
|
||||
return Err(AppError::Validation("currency must be a 3-letter code".into()));
|
||||
}
|
||||
}
|
||||
|
||||
let settings = sqlx::query_as::<_, UserSettings>(
|
||||
"UPDATE user_settings SET
|
||||
locale = COALESCE($2, locale),
|
||||
currency = COALESCE($3, currency),
|
||||
theme = COALESCE($4, theme),
|
||||
notify_email = COALESCE($5, notify_email)
|
||||
WHERE user_id = $1
|
||||
RETURNING user_id, locale, currency, theme, notify_email",
|
||||
)
|
||||
.bind(user.id)
|
||||
.bind(req.locale)
|
||||
.bind(req.currency.map(|c| c.to_uppercase()))
|
||||
.bind(req.theme)
|
||||
.bind(req.notify_email)
|
||||
.fetch_one(&state.pool)
|
||||
.await?;
|
||||
|
||||
Ok(Json(settings))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Validate)]
|
||||
struct ProfileReq {
|
||||
#[validate(length(max = 80, message = "display name too long"))]
|
||||
display_name: Option<String>,
|
||||
}
|
||||
|
||||
async fn update_profile(
|
||||
State(state): State<AppState>,
|
||||
AuthUser(user): AuthUser,
|
||||
Json(req): Json<ProfileReq>,
|
||||
) -> AppResult<Json<Value>> {
|
||||
req.validate()
|
||||
.map_err(|e| AppError::Validation(e.to_string()))?;
|
||||
|
||||
let display = req.display_name.as_deref().map(str::trim).filter(|s| !s.is_empty());
|
||||
sqlx::query("UPDATE users SET display_name = $2 WHERE id = $1")
|
||||
.bind(user.id)
|
||||
.bind(display)
|
||||
.execute(&state.pool)
|
||||
.await?;
|
||||
|
||||
Ok(Json(json!({ "display_name": display })))
|
||||
}
|
||||
Reference in New Issue
Block a user