Skip to main content

cloudillo_core/settings/
handler.rs

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