From c0b681adc33c5ac1fc236805c70b8b2daf3603f1 Mon Sep 17 00:00:00 2001 From: Fam Zheng Date: Wed, 4 Mar 2026 11:46:58 +0000 Subject: [PATCH] feat: add settings API with key-value store GET/PUT endpoints for app settings backed by a settings table. --- src/api/settings.rs | 96 +++++++++++++++++++++++++++++++++++++++++++++ src/db.rs | 9 +++++ 2 files changed, 105 insertions(+) create mode 100644 src/api/settings.rs diff --git a/src/api/settings.rs b/src/api/settings.rs new file mode 100644 index 0000000..c1de1ff --- /dev/null +++ b/src/api/settings.rs @@ -0,0 +1,96 @@ +use std::collections::HashMap; +use std::sync::Arc; +use axum::{ + extract::{Path, State}, + http::StatusCode, + response::{IntoResponse, Response}, + routing::get, + Json, Router, +}; +use serde::Deserialize; +use crate::AppState; +use super::{ApiResult, db_err}; + +#[derive(Deserialize)] +pub struct PutSetting { + pub value: String, +} + +pub fn router(state: Arc) -> Router { + Router::new() + .route("/settings", get(list_settings)) + .route("/settings/{key}", get(get_setting).put(put_setting).delete(delete_setting)) + .with_state(state) +} + +async fn list_settings( + State(state): State>, +) -> ApiResult> { + let rows: Vec<(String, String)> = sqlx::query_as( + "SELECT key, value FROM settings ORDER BY key" + ) + .fetch_all(&state.db.pool) + .await + .map_err(db_err)?; + + let map: HashMap = rows.into_iter().collect(); + Ok(Json(map)) +} + +async fn get_setting( + State(state): State>, + Path(key): Path, +) -> ApiResult> { + let row: Option<(String,)> = sqlx::query_as( + "SELECT value FROM settings WHERE key = ?" + ) + .bind(&key) + .fetch_optional(&state.db.pool) + .await + .map_err(db_err)?; + + match row { + Some((value,)) => { + let mut map = HashMap::new(); + map.insert(key, value); + Ok(Json(map)) + } + None => Err((StatusCode::NOT_FOUND, "Setting not found").into_response()), + } +} + +async fn put_setting( + State(state): State>, + Path(key): Path, + Json(input): Json, +) -> ApiResult> { + sqlx::query( + "INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value" + ) + .bind(&key) + .bind(&input.value) + .execute(&state.db.pool) + .await + .map_err(db_err)?; + + let mut map = HashMap::new(); + map.insert(key, input.value); + Ok(Json(map)) +} + +async fn delete_setting( + State(state): State>, + Path(key): Path, +) -> Result { + let result = sqlx::query("DELETE FROM settings WHERE key = ?") + .bind(&key) + .execute(&state.db.pool) + .await + .map_err(db_err)?; + + if result.rows_affected() > 0 { + Ok(StatusCode::NO_CONTENT) + } else { + Err((StatusCode::NOT_FOUND, "Setting not found").into_response()) + } +} diff --git a/src/db.rs b/src/db.rs index db8686d..d1f3180 100644 --- a/src/db.rs +++ b/src/db.rs @@ -199,6 +199,15 @@ impl Database { .execute(&self.pool) .await?; + sqlx::query( + "CREATE TABLE IF NOT EXISTS settings ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL + )" + ) + .execute(&self.pool) + .await?; + Ok(()) } }