Skip to main content

cloudillo_core/settings/
handler.rs

1//! Settings management handlers
2
3use axum::{
4	extract::{Path, Query, State},
5	http::StatusCode,
6	Json,
7};
8use serde::Deserialize;
9
10use crate::{
11	extract::{Auth, OptionalRequestId},
12	prelude::*,
13	settings::types::SettingValue,
14};
15use cloudillo_types::types::ApiResponse;
16
17/// Response for a single setting with metadata
18#[derive(serde::Serialize)]
19pub struct SettingResponse {
20	pub key: String,
21	pub value: SettingValue,
22	pub scope: String,
23	pub permission: String,
24	pub description: String,
25}
26
27/// Query parameters for listing settings
28#[derive(Deserialize, Default)]
29pub struct ListSettingsQuery {
30	/// Filter settings by key prefix (e.g., "ui", "notify")
31	pub prefix: Option<String>,
32}
33
34/// GET /settings - List all settings for authenticated tenant
35/// Returns metadata about available settings and their current values
36/// Supports optional `prefix` query parameter to filter settings by key prefix
37pub async fn list_settings(
38	State(app): State<App>,
39	Auth(auth): Auth,
40	Query(query): Query<ListSettingsQuery>,
41	OptionalRequestId(req_id): OptionalRequestId,
42) -> ClResult<(StatusCode, Json<ApiResponse<Vec<SettingResponse>>>)> {
43	let mut settings_response = Vec::new();
44
45	if let Some(ref prefix) = query.prefix {
46		// Query stored settings from database matching prefix
47		// Uses wildcard pattern matching (e.g., "ui.theme" matches "ui.*" definition)
48		for (key, value, definition) in app.settings.list_by_prefix(auth.tn_id, prefix).await? {
49			settings_response.push(SettingResponse {
50				key,
51				value,
52				scope: format!("{:?}", definition.scope),
53				permission: format!("{:?}", definition.permission),
54				description: definition.description.clone(),
55			});
56		}
57	} else {
58		// No prefix: iterate over all definitions and get their values
59		for definition in app.settings_registry.list() {
60			if let Ok(value) = app.settings.get(auth.tn_id, &definition.key).await {
61				settings_response.push(SettingResponse {
62					key: definition.key.clone(),
63					value,
64					scope: format!("{:?}", definition.scope),
65					permission: format!("{:?}", definition.permission),
66					description: definition.description.clone(),
67				});
68			}
69		}
70	}
71
72	let total = settings_response.len();
73	let response = ApiResponse::with_pagination(settings_response, 0, 100, total)
74		.with_req_id(req_id.unwrap_or_default());
75
76	Ok((StatusCode::OK, Json(response)))
77}
78
79/// GET /settings/:name - Get a specific setting with metadata
80pub async fn get_setting(
81	State(app): State<App>,
82	Auth(auth): Auth,
83	Path(name): Path<String>,
84	OptionalRequestId(req_id): OptionalRequestId,
85) -> ClResult<(StatusCode, Json<ApiResponse<SettingResponse>>)> {
86	// Get setting definition (supports wildcard patterns like "ui.*")
87	let definition = app.settings_registry.get(&name).ok_or(Error::NotFound)?;
88
89	// Get current value with three-level resolution
90	let value = app.settings.get(auth.tn_id, &name).await?;
91
92	let response_data = SettingResponse {
93		key: definition.key.clone(),
94		value,
95		scope: format!("{:?}", definition.scope),
96		permission: format!("{:?}", definition.permission),
97		description: definition.description.clone(),
98	};
99
100	let response = ApiResponse::new(response_data).with_req_id(req_id.unwrap_or_default());
101
102	Ok((StatusCode::OK, Json(response)))
103}
104
105/// PUT /settings/:name - Update a setting
106/// Requires appropriate permission level (admin for most, user for some)
107#[derive(Deserialize)]
108pub struct UpdateSettingRequest {
109	pub value: SettingValue,
110}
111
112pub async fn update_setting(
113	State(app): State<App>,
114	Auth(auth): Auth,
115	Path(name): Path<String>,
116	OptionalRequestId(req_id): OptionalRequestId,
117	Json(req): Json<UpdateSettingRequest>,
118) -> ClResult<(StatusCode, Json<ApiResponse<SettingResponse>>)> {
119	// Get setting definition for validation and permission check
120	let definition = app.settings_registry.get(&name).ok_or(Error::NotFound)?;
121
122	// Check permission
123	if !definition.permission.check(&auth.roles) {
124		warn!("User {} attempted to update setting {} without permission", auth.id_tag, name);
125		return Err(Error::PermissionDenied);
126	}
127
128	// Validate value if validator is set
129	if let Some(ref validator) = definition.validator {
130		validator(&req.value)?;
131	}
132
133	// Update the setting using the service
134	app.settings.set(auth.tn_id, &name, req.value.clone(), &auth.roles).await?;
135
136	info!("User {} updated setting {} in tenant {}", auth.id_tag, name, auth.tn_id);
137
138	// Return updated setting
139	let value = app.settings.get(auth.tn_id, &name).await?;
140
141	let response_data = SettingResponse {
142		key: definition.key.clone(),
143		value,
144		scope: format!("{:?}", definition.scope),
145		permission: format!("{:?}", definition.permission),
146		description: definition.description.clone(),
147	};
148
149	let response = ApiResponse::new(response_data).with_req_id(req_id.unwrap_or_default());
150
151	Ok((StatusCode::OK, Json(response)))
152}
153
154// vim: ts=4