allsource_core/infrastructure/web/
config_api.rs1use crate::{
2 error::{AllSourceError, Result},
3 infrastructure::{security::middleware::Admin, web::api_v1::AppState},
4};
5use axum::{Json, extract::State, http::StatusCode};
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Deserialize)]
14pub struct SetConfigRequest {
15 pub key: String,
16 pub value: serde_json::Value,
17 pub changed_by: Option<String>,
18}
19
20#[derive(Debug, Deserialize)]
21pub struct UpdateConfigRequest {
22 pub value: serde_json::Value,
23 pub changed_by: Option<String>,
24}
25
26#[derive(Debug, Serialize)]
27pub struct ConfigEntryResponse {
28 pub key: String,
29 pub value: serde_json::Value,
30 pub updated_at: DateTime<Utc>,
31 pub updated_by: Option<String>,
32}
33
34#[derive(Debug, Serialize)]
35pub struct ListConfigsResponse {
36 pub configs: Vec<ConfigEntryResponse>,
37 pub total: usize,
38}
39
40pub async fn list_configs(
47 State(state): State<AppState>,
48 Admin(_): Admin,
49) -> Result<Json<ListConfigsResponse>> {
50 let config_repo = state
51 .service_container
52 .config_repository()
53 .ok_or_else(|| AllSourceError::InternalError("Config repository not configured".into()))?;
54
55 let entries = config_repo.list();
56 let total = entries.len();
57
58 let configs: Vec<ConfigEntryResponse> = entries
59 .into_iter()
60 .map(|e| ConfigEntryResponse {
61 key: e.key,
62 value: e.value,
63 updated_at: e.updated_at,
64 updated_by: e.updated_by,
65 })
66 .collect();
67
68 Ok(Json(ListConfigsResponse { configs, total }))
69}
70
71pub async fn get_config(
74 State(state): State<AppState>,
75 Admin(_): Admin,
76 axum::extract::Path(key): axum::extract::Path<String>,
77) -> Result<Json<ConfigEntryResponse>> {
78 let config_repo = state
79 .service_container
80 .config_repository()
81 .ok_or_else(|| AllSourceError::InternalError("Config repository not configured".into()))?;
82
83 let entry = config_repo
84 .get(&key)
85 .ok_or_else(|| AllSourceError::EntityNotFound(format!("Config key not found: {key}")))?;
86
87 Ok(Json(ConfigEntryResponse {
88 key: entry.key,
89 value: entry.value,
90 updated_at: entry.updated_at,
91 updated_by: entry.updated_by,
92 }))
93}
94
95pub async fn set_config(
98 State(state): State<AppState>,
99 Admin(_): Admin,
100 Json(req): Json<SetConfigRequest>,
101) -> Result<(StatusCode, Json<serde_json::Value>)> {
102 let config_repo = state
103 .service_container
104 .config_repository()
105 .ok_or_else(|| AllSourceError::InternalError("Config repository not configured".into()))?;
106
107 if req.key.is_empty() {
108 return Err(AllSourceError::InvalidInput(
109 "Config key cannot be empty".into(),
110 ));
111 }
112
113 config_repo.set(&req.key, req.value, req.changed_by.as_deref())?;
114
115 tracing::debug!("Config set: {}", req.key);
116
117 Ok((
118 StatusCode::OK,
119 Json(serde_json::json!({
120 "key": req.key,
121 "saved": true,
122 })),
123 ))
124}
125
126pub async fn update_config(
129 State(state): State<AppState>,
130 Admin(_): Admin,
131 axum::extract::Path(key): axum::extract::Path<String>,
132 Json(req): Json<UpdateConfigRequest>,
133) -> Result<Json<ConfigEntryResponse>> {
134 let config_repo = state
135 .service_container
136 .config_repository()
137 .ok_or_else(|| AllSourceError::InternalError("Config repository not configured".into()))?;
138
139 if config_repo.get(&key).is_none() {
141 return Err(AllSourceError::EntityNotFound(format!(
142 "Config key not found: {key}"
143 )));
144 }
145
146 config_repo.set(&key, req.value.clone(), req.changed_by.as_deref())?;
147
148 let entry = config_repo.get(&key).ok_or_else(|| {
149 AllSourceError::InternalError("Config entry disappeared after update".into())
150 })?;
151
152 tracing::debug!("Config updated: {}", key);
153
154 Ok(Json(ConfigEntryResponse {
155 key: entry.key,
156 value: entry.value,
157 updated_at: entry.updated_at,
158 updated_by: entry.updated_by,
159 }))
160}
161
162pub async fn delete_config(
165 State(state): State<AppState>,
166 Admin(_): Admin,
167 axum::extract::Path(key): axum::extract::Path<String>,
168) -> Result<StatusCode> {
169 let config_repo = state
170 .service_container
171 .config_repository()
172 .ok_or_else(|| AllSourceError::InternalError("Config repository not configured".into()))?;
173
174 let deleted = config_repo.delete(&key, None)?;
175
176 if !deleted {
177 return Err(AllSourceError::EntityNotFound(format!(
178 "Config key not found: {key}"
179 )));
180 }
181
182 tracing::debug!("Config deleted: {}", key);
183
184 Ok(StatusCode::NO_CONTENT)
185}