summaryrefslogtreecommitdiff
path: root/makima/src/server/handlers/settings.rs
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-04-28 00:18:40 +0100
committerGitHub <noreply@github.com>2026-04-28 00:18:40 +0100
commitc8b169da8cb7eae0957e0ab5e7370b071093a224 (patch)
treec3f9720a8acfe863ac0b65df9439abf9a941323a /makima/src/server/handlers/settings.rs
parent3679ceb3325033faa2f889ef3dfee5668ef7aeea (diff)
downloadsoryu-c8b169da8cb7eae0957e0ab5e7370b071093a224.tar.gz
soryu-c8b169da8cb7eae0957e0ab5e7370b071093a224.zip
feat: Document UI for directive orchestration with Lexical editor (#93)
* WIP: heartbeat checkpoint * feat: soryu-co/soryu - makima: Save previous goal on update and include history in re-planning prompt * feat: soryu-co/soryu - makima: Install Lexical and create base document editor component * feat: soryu-co/soryu - makima: Create directive file system sidebar and document layout * feat: soryu-co/soryu - makima: Create custom Lexical step diagram block * feat: soryu-co/soryu - makima: Add context menu and goal auto-update integration * WIP: heartbeat checkpoint
Diffstat (limited to 'makima/src/server/handlers/settings.rs')
-rw-r--r--makima/src/server/handlers/settings.rs196
1 files changed, 196 insertions, 0 deletions
diff --git a/makima/src/server/handlers/settings.rs b/makima/src/server/handlers/settings.rs
new file mode 100644
index 0000000..ae52d5a
--- /dev/null
+++ b/makima/src/server/handlers/settings.rs
@@ -0,0 +1,196 @@
+//! HTTP handlers for user settings (feature flags / preferences).
+
+use axum::{
+ extract::{Path, State},
+ http::StatusCode,
+ response::IntoResponse,
+ Json,
+};
+
+use crate::db::models::{UpsertUserSettingRequest, UserSettingsResponse};
+use crate::db::repository;
+use crate::server::auth::Authenticated;
+use crate::server::messages::ApiError;
+use crate::server::state::SharedState;
+
+/// List all settings for the authenticated user.
+#[utoipa::path(
+ get,
+ path = "/api/v1/settings",
+ responses(
+ (status = 200, description = "User settings", body = UserSettingsResponse),
+ (status = 401, description = "Not authenticated", body = ApiError),
+ (status = 503, description = "Database not configured", body = ApiError),
+ (status = 500, description = "Internal server error", body = ApiError),
+ ),
+ security(
+ ("bearer_auth" = [])
+ ),
+ tag = "Settings"
+)]
+pub async fn list_settings(
+ State(state): State<SharedState>,
+ Authenticated(user): Authenticated,
+) -> impl IntoResponse {
+ let Some(ref pool) = state.db_pool else {
+ return (
+ StatusCode::SERVICE_UNAVAILABLE,
+ Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
+ )
+ .into_response();
+ };
+
+ match repository::get_user_settings(pool, user.owner_id).await {
+ Ok(settings) => Json(UserSettingsResponse { settings }).into_response(),
+ Err(e) => {
+ tracing::error!("Failed to list user settings: {}", e);
+ (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(ApiError::new("DB_ERROR", e.to_string())),
+ )
+ .into_response()
+ }
+ }
+}
+
+/// Get a specific setting by key.
+#[utoipa::path(
+ get,
+ path = "/api/v1/settings/{key}",
+ params(
+ ("key" = String, Path, description = "Setting key")
+ ),
+ responses(
+ (status = 200, description = "User setting", body = crate::db::models::UserSetting),
+ (status = 401, description = "Not authenticated", body = ApiError),
+ (status = 404, description = "Setting not found", body = ApiError),
+ (status = 503, description = "Database not configured", body = ApiError),
+ (status = 500, description = "Internal server error", body = ApiError),
+ ),
+ security(
+ ("bearer_auth" = [])
+ ),
+ tag = "Settings"
+)]
+pub async fn get_setting(
+ State(state): State<SharedState>,
+ Authenticated(user): Authenticated,
+ Path(key): Path<String>,
+) -> impl IntoResponse {
+ let Some(ref pool) = state.db_pool else {
+ return (
+ StatusCode::SERVICE_UNAVAILABLE,
+ Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
+ )
+ .into_response();
+ };
+
+ match repository::get_user_setting(pool, user.owner_id, &key).await {
+ Ok(Some(setting)) => Json(setting).into_response(),
+ Ok(None) => (
+ StatusCode::NOT_FOUND,
+ Json(ApiError::new("NOT_FOUND", format!("Setting '{}' not found", key))),
+ )
+ .into_response(),
+ Err(e) => {
+ tracing::error!("Failed to get user setting: {}", e);
+ (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(ApiError::new("DB_ERROR", e.to_string())),
+ )
+ .into_response()
+ }
+ }
+}
+
+/// Upsert a user setting (create or update).
+#[utoipa::path(
+ put,
+ path = "/api/v1/settings",
+ request_body = UpsertUserSettingRequest,
+ responses(
+ (status = 200, description = "Setting upserted", body = crate::db::models::UserSetting),
+ (status = 401, description = "Not authenticated", body = ApiError),
+ (status = 503, description = "Database not configured", body = ApiError),
+ (status = 500, description = "Internal server error", body = ApiError),
+ ),
+ security(
+ ("bearer_auth" = [])
+ ),
+ tag = "Settings"
+)]
+pub async fn upsert_setting(
+ State(state): State<SharedState>,
+ Authenticated(user): Authenticated,
+ Json(req): Json<UpsertUserSettingRequest>,
+) -> impl IntoResponse {
+ let Some(ref pool) = state.db_pool else {
+ return (
+ StatusCode::SERVICE_UNAVAILABLE,
+ Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
+ )
+ .into_response();
+ };
+
+ match repository::upsert_user_setting(pool, user.owner_id, &req.key, &req.value).await {
+ Ok(setting) => Json(setting).into_response(),
+ Err(e) => {
+ tracing::error!("Failed to upsert user setting: {}", e);
+ (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(ApiError::new("DB_ERROR", e.to_string())),
+ )
+ .into_response()
+ }
+ }
+}
+
+/// Delete a user setting by key.
+#[utoipa::path(
+ delete,
+ path = "/api/v1/settings/{key}",
+ params(
+ ("key" = String, Path, description = "Setting key")
+ ),
+ responses(
+ (status = 200, description = "Setting deleted"),
+ (status = 401, description = "Not authenticated", body = ApiError),
+ (status = 404, description = "Setting not found", body = ApiError),
+ (status = 503, description = "Database not configured", body = ApiError),
+ (status = 500, description = "Internal server error", body = ApiError),
+ ),
+ security(
+ ("bearer_auth" = [])
+ ),
+ tag = "Settings"
+)]
+pub async fn delete_setting(
+ State(state): State<SharedState>,
+ Authenticated(user): Authenticated,
+ Path(key): Path<String>,
+) -> impl IntoResponse {
+ let Some(ref pool) = state.db_pool else {
+ return (
+ StatusCode::SERVICE_UNAVAILABLE,
+ Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
+ )
+ .into_response();
+ };
+
+ match repository::delete_user_setting(pool, user.owner_id, &key).await {
+ Ok(true) => StatusCode::OK.into_response(),
+ Ok(false) => (
+ StatusCode::NOT_FOUND,
+ Json(ApiError::new("NOT_FOUND", format!("Setting '{}' not found", key))),
+ )
+ .into_response(),
+ Err(e) => {
+ tracing::error!("Failed to delete user setting: {}", e);
+ (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(ApiError::new("DB_ERROR", e.to_string())),
+ )
+ .into_response()
+ }
+ }
+}