1use axum::{
2 extract::{Path, State},
3 http::StatusCode,
4 response::{IntoResponse, Json},
5 routing::{get, post},
6 Router,
7};
8use gitent_core::{Change, ChangeType, Commit, CommitInfo, Session, Storage};
9use serde::Deserialize;
10use std::sync::{Arc, Mutex};
11use uuid::Uuid;
12
13#[derive(Clone)]
14pub struct AppState {
15 pub storage: Arc<Mutex<Storage>>,
16}
17
18pub fn create_router(state: AppState) -> Router {
19 Router::new()
20 .route("/health", get(health_check))
21 .route("/session", get(get_active_session))
22 .route("/changes", get(get_uncommitted_changes))
23 .route("/changes", post(create_change))
24 .route("/commits", get(get_commits))
25 .route("/commits", post(create_commit))
26 .route("/commits/:id", get(get_commit))
27 .with_state(state)
28}
29
30async fn health_check() -> impl IntoResponse {
31 Json(serde_json::json!({"status": "ok"}))
32}
33
34async fn get_active_session(
35 State(state): State<AppState>,
36) -> Result<Json<Session>, (StatusCode, String)> {
37 let storage = state.storage.lock().unwrap();
38 storage
39 .get_active_session()
40 .map(Json)
41 .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))
42}
43
44async fn get_uncommitted_changes(
45 State(state): State<AppState>,
46) -> Result<Json<Vec<Change>>, (StatusCode, String)> {
47 let storage = state.storage.lock().unwrap();
48 let session = storage
49 .get_active_session()
50 .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
51
52 storage
53 .get_uncommitted_changes(&session.id)
54 .map(Json)
55 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))
56}
57
58#[derive(Deserialize)]
59struct CreateChangeRequest {
60 change_type: String,
61 path: String,
62 content_before: Option<String>,
63 content_after: Option<String>,
64 agent_id: Option<String>,
65}
66
67async fn create_change(
68 State(state): State<AppState>,
69 Json(req): Json<CreateChangeRequest>,
70) -> Result<Json<Change>, (StatusCode, String)> {
71 let storage = state.storage.lock().unwrap();
72 let session = storage
73 .get_active_session()
74 .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
75
76 let change_type = ChangeType::parse(&req.change_type)
77 .ok_or_else(|| (StatusCode::BAD_REQUEST, "Invalid change type".to_string()))?;
78
79 let mut change = Change::new(change_type, std::path::PathBuf::from(req.path), session.id);
80
81 if let Some(content) = req.content_before {
82 change = change.with_content_before(content.into_bytes());
83 }
84
85 if let Some(content) = req.content_after {
86 change = change.with_content_after(content.into_bytes());
87 }
88
89 if let Some(agent_id) = req.agent_id {
90 change = change.with_agent_id(agent_id);
91 }
92
93 storage
94 .create_change(&change)
95 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
96
97 Ok(Json(change))
98}
99
100async fn get_commits(
101 State(state): State<AppState>,
102) -> Result<Json<Vec<CommitInfo>>, (StatusCode, String)> {
103 let storage = state.storage.lock().unwrap();
104 let session = storage
105 .get_active_session()
106 .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
107
108 storage
109 .get_commits_for_session(&session.id)
110 .map(Json)
111 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))
112}
113
114#[derive(Deserialize)]
115struct CreateCommitRequest {
116 message: String,
117 agent_id: String,
118 change_ids: Vec<String>,
119}
120
121async fn create_commit(
122 State(state): State<AppState>,
123 Json(req): Json<CreateCommitRequest>,
124) -> Result<Json<Commit>, (StatusCode, String)> {
125 let storage = state.storage.lock().unwrap();
126 let session = storage
127 .get_active_session()
128 .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))?;
129
130 let change_ids: Vec<Uuid> = req
131 .change_ids
132 .iter()
133 .filter_map(|id| Uuid::parse_str(id).ok())
134 .collect();
135
136 let commit = Commit::new(req.message, req.agent_id, change_ids, session.id);
137
138 storage
139 .create_commit(&commit)
140 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
141
142 Ok(Json(commit))
143}
144
145async fn get_commit(
146 State(state): State<AppState>,
147 Path(id): Path<String>,
148) -> Result<Json<Commit>, (StatusCode, String)> {
149 let commit_id =
150 Uuid::parse_str(&id).map_err(|_| (StatusCode::BAD_REQUEST, "Invalid UUID".to_string()))?;
151
152 let storage = state.storage.lock().unwrap();
153 storage
154 .get_commit(&commit_id)
155 .map(Json)
156 .map_err(|e| (StatusCode::NOT_FOUND, e.to_string()))
157}