flow_server/routes/
features.rs1use crate::{
2 error::{AppError, AppResult},
3 state::AppState,
4};
5use axum::{
6 extract::{Path, State},
7 response::Json,
8 routing::{delete, get, post},
9 Router,
10};
11use flow_core::{CreateFeatureInput, DependencyRef, Feature, FeatureGraphNode, FeatureStats};
12use flow_db::FeatureStore;
13use serde::Deserialize;
14use std::sync::Arc;
15
16#[derive(Debug, Deserialize)]
17pub struct BulkCreateRequest {
18 features: Vec<CreateFeatureInput>,
19}
20
21pub fn feature_routes() -> Router<Arc<AppState>> {
23 Router::new()
24 .route("/", get(list_features).post(create_feature))
25 .route("/bulk", post(bulk_create))
26 .route("/stats", get(get_stats))
27 .route("/ready", get(get_ready))
28 .route("/blocked", get(get_blocked))
29 .route("/graph", get(get_graph))
30 .route("/:id", get(get_feature_by_id))
31 .route("/:id/claim", post(claim_feature))
32 .route("/:id/pass", post(mark_passing))
33 .route("/:id/fail", post(mark_failing))
34 .route("/:id/skip", post(skip_feature))
35 .route("/:id/dependencies", post(add_dependency))
36 .route("/:id/dependencies/:dep_id", delete(remove_dependency))
37}
38
39async fn list_features(State(state): State<Arc<AppState>>) -> AppResult<Json<Vec<Feature>>> {
41 let db = state
42 .db
43 .as_ref()
44 .ok_or_else(|| AppError::Internal("Database not configured".into()))?;
45
46 let features = {
47 let conn = db.writer().lock().unwrap();
48 FeatureStore::get_all(&conn)?
49 };
50 Ok(Json(features))
51}
52
53async fn get_feature_by_id(
55 State(state): State<Arc<AppState>>,
56 Path(id): Path<String>,
57) -> AppResult<Json<Feature>> {
58 let db = state
59 .db
60 .as_ref()
61 .ok_or_else(|| AppError::Internal("Database not configured".into()))?;
62
63 let id_num: i64 = id
64 .parse()
65 .map_err(|_| AppError::BadRequest("Invalid feature ID".into()))?;
66
67 let feature = {
68 let conn = db.writer().lock().unwrap();
69 FeatureStore::get_by_id(&conn, id_num)?
70 .ok_or_else(|| AppError::NotFound("Feature not found".into()))?
71 };
72 Ok(Json(feature))
73}
74
75async fn get_stats(State(state): State<Arc<AppState>>) -> AppResult<Json<FeatureStats>> {
77 let db = state
78 .db
79 .as_ref()
80 .ok_or_else(|| AppError::Internal("Database not configured".into()))?;
81
82 let feature_stats = {
83 let conn = db.writer().lock().unwrap();
84 FeatureStore::get_stats(&conn)?
85 };
86 Ok(Json(feature_stats))
87}
88
89async fn get_ready(State(state): State<Arc<AppState>>) -> AppResult<Json<Vec<Feature>>> {
91 let db = state
92 .db
93 .as_ref()
94 .ok_or_else(|| AppError::Internal("Database not configured".into()))?;
95
96 let features = {
97 let conn = db.writer().lock().unwrap();
98 FeatureStore::get_ready(&conn)?
99 };
100 Ok(Json(features))
101}
102
103async fn get_blocked(State(state): State<Arc<AppState>>) -> AppResult<Json<Vec<Feature>>> {
105 let db = state
106 .db
107 .as_ref()
108 .ok_or_else(|| AppError::Internal("Database not configured".into()))?;
109
110 let features = {
111 let conn = db.writer().lock().unwrap();
112 FeatureStore::get_blocked(&conn)?
113 };
114 Ok(Json(features))
115}
116
117async fn get_graph(State(state): State<Arc<AppState>>) -> AppResult<Json<Vec<FeatureGraphNode>>> {
119 let db = state
120 .db
121 .as_ref()
122 .ok_or_else(|| AppError::Internal("Database not configured".into()))?;
123
124 let features = {
125 let conn = db.writer().lock().unwrap();
126 FeatureStore::get_all(&conn)?
127 };
128
129 let mut dependents_map: std::collections::HashMap<i64, Vec<i64>> =
131 std::collections::HashMap::new();
132 for feature in &features {
133 for &dep_id in &feature.dependencies {
134 dependents_map.entry(dep_id).or_default().push(feature.id);
135 }
136 }
137
138 let features_for_check = features.clone();
140
141 let graph: Vec<FeatureGraphNode> = features
143 .into_iter()
144 .map(|f| {
145 let is_blocked = !f.dependencies.is_empty()
146 && f.dependencies.iter().any(|dep_id| {
147 features_for_check
148 .iter()
149 .any(|other| other.id == *dep_id && !other.passes)
150 });
151
152 FeatureGraphNode {
153 id: f.id,
154 name: f.name,
155 category: f.category,
156 priority: f.priority,
157 passes: f.passes,
158 in_progress: f.in_progress,
159 blocked: is_blocked,
160 dependencies: f.dependencies.clone(),
161 dependents: dependents_map.get(&f.id).cloned().unwrap_or_default(),
162 }
163 })
164 .collect();
165
166 Ok(Json(graph))
167}
168
169async fn create_feature(
171 State(state): State<Arc<AppState>>,
172 Json(input): Json<CreateFeatureInput>,
173) -> AppResult<Json<Feature>> {
174 let db = state
175 .db
176 .as_ref()
177 .ok_or_else(|| AppError::Internal("Database not configured".into()))?;
178
179 let feature = {
180 let conn = db.writer().lock().unwrap();
181 FeatureStore::create(&conn, &input)?
182 };
183 Ok(Json(feature))
184}
185
186async fn bulk_create(
188 State(state): State<Arc<AppState>>,
189 Json(request): Json<BulkCreateRequest>,
190) -> AppResult<Json<Vec<Feature>>> {
191 let db = state
192 .db
193 .as_ref()
194 .ok_or_else(|| AppError::Internal("Database not configured".into()))?;
195
196 let features = {
197 let conn = db.writer().lock().unwrap();
198 FeatureStore::create_bulk(&conn, &request.features)?
199 };
200 Ok(Json(features))
201}
202
203async fn claim_feature(
205 State(state): State<Arc<AppState>>,
206 Path(id): Path<String>,
207) -> AppResult<Json<Feature>> {
208 let db = state
209 .db
210 .as_ref()
211 .ok_or_else(|| AppError::Internal("Database not configured".into()))?;
212
213 let id_num: i64 = id
214 .parse()
215 .map_err(|_| AppError::BadRequest("Invalid feature ID".into()))?;
216
217 let feature = {
218 let conn = db.writer().lock().unwrap();
219 FeatureStore::claim_and_get(&conn, id_num)?
220 };
221 Ok(Json(feature))
222}
223
224async fn mark_passing(
226 State(state): State<Arc<AppState>>,
227 Path(id): Path<String>,
228) -> AppResult<Json<serde_json::Value>> {
229 let db = state
230 .db
231 .as_ref()
232 .ok_or_else(|| AppError::Internal("Database not configured".into()))?;
233
234 let id_num: i64 = id
235 .parse()
236 .map_err(|_| AppError::BadRequest("Invalid feature ID".into()))?;
237
238 let conn = db.writer().lock().unwrap();
239 FeatureStore::mark_passing(&conn, id_num)?;
240 Ok(Json(serde_json::json!({ "success": true })))
241}
242
243async fn mark_failing(
245 State(state): State<Arc<AppState>>,
246 Path(id): Path<String>,
247) -> AppResult<Json<serde_json::Value>> {
248 let db = state
249 .db
250 .as_ref()
251 .ok_or_else(|| AppError::Internal("Database not configured".into()))?;
252
253 let id_num: i64 = id
254 .parse()
255 .map_err(|_| AppError::BadRequest("Invalid feature ID".into()))?;
256
257 let conn = db.writer().lock().unwrap();
258 FeatureStore::mark_failing(&conn, id_num)?;
259 Ok(Json(serde_json::json!({ "success": true })))
260}
261
262async fn skip_feature(
264 State(state): State<Arc<AppState>>,
265 Path(id): Path<String>,
266) -> AppResult<Json<serde_json::Value>> {
267 let db = state
268 .db
269 .as_ref()
270 .ok_or_else(|| AppError::Internal("Database not configured".into()))?;
271
272 let id_num: i64 = id
273 .parse()
274 .map_err(|_| AppError::BadRequest("Invalid feature ID".into()))?;
275
276 let conn = db.writer().lock().unwrap();
277 FeatureStore::skip(&conn, id_num)?;
278 Ok(Json(serde_json::json!({ "success": true })))
279}
280
281async fn add_dependency(
283 State(state): State<Arc<AppState>>,
284 Path(id): Path<String>,
285 Json(dep): Json<DependencyRef>,
286) -> AppResult<Json<serde_json::Value>> {
287 let db = state
288 .db
289 .as_ref()
290 .ok_or_else(|| AppError::Internal("Database not configured".into()))?;
291
292 let id_num: i64 = id
293 .parse()
294 .map_err(|_| AppError::BadRequest("Invalid feature ID".into()))?;
295
296 let DependencyRef::Id(dep_id) = dep else {
298 return Err(AppError::BadRequest("Invalid dependency reference".into()));
299 };
300
301 let conn = db.writer().lock().unwrap();
302 FeatureStore::add_dependency(&conn, id_num, dep_id)?;
303 Ok(Json(serde_json::json!({ "success": true })))
304}
305
306async fn remove_dependency(
308 State(state): State<Arc<AppState>>,
309 Path((id, dep_id)): Path<(String, String)>,
310) -> AppResult<Json<serde_json::Value>> {
311 let db = state
312 .db
313 .as_ref()
314 .ok_or_else(|| AppError::Internal("Database not configured".into()))?;
315
316 let id_num: i64 = id
317 .parse()
318 .map_err(|_| AppError::BadRequest("Invalid feature ID".into()))?;
319
320 let dep_id_num: i64 = dep_id
321 .parse()
322 .map_err(|_| AppError::BadRequest("Invalid dependency ID".into()))?;
323
324 let conn = db.writer().lock().unwrap();
325 FeatureStore::remove_dependency(&conn, id_num, dep_id_num)?;
326 Ok(Json(serde_json::json!({ "success": true })))
327}