Skip to main content

vibe_graph_api/routes/
mod.rs

1//! API route handlers.
2
3pub mod file;
4pub mod git;
5mod graph;
6mod health;
7pub mod ops;
8
9use std::path::PathBuf;
10use std::sync::Arc;
11
12use axum::{
13    routing::{delete, get, post},
14    Router,
15};
16use tower_http::cors::{Any, CorsLayer};
17use tower_http::trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer};
18use tracing::Level;
19use vibe_graph_ops::OpsContext;
20
21use crate::types::ApiState;
22use crate::ws::ws_handler;
23use file::FileState;
24use git::GitOpsState;
25use ops::OpsState;
26
27/// Create the API router with all endpoints.
28pub fn create_api_router(state: Arc<ApiState>) -> Router {
29    let cors = CorsLayer::new()
30        .allow_origin(Any)
31        .allow_methods(Any)
32        .allow_headers(Any);
33
34    Router::new()
35        // Health
36        .route("/health", get(health::health_handler))
37        // Graph endpoints
38        .route("/graph", get(graph::graph_handler))
39        .route("/graph/nodes", get(graph::nodes_handler))
40        .route("/graph/edges", get(graph::edges_handler))
41        .route("/graph/metadata", get(graph::metadata_handler))
42        // Git endpoints
43        .route("/git/changes", get(git::changes_handler))
44        // WebSocket
45        .route("/ws", get(ws_handler))
46        // Request tracing (enable with RUST_LOG=tower_http=info or higher)
47        .layer(
48            TraceLayer::new_for_http()
49                .make_span_with(
50                    DefaultMakeSpan::new()
51                        .level(Level::INFO)
52                        .include_headers(false),
53                )
54                .on_response(DefaultOnResponse::new().level(Level::INFO)),
55        )
56        .layer(cors)
57        .with_state(state)
58}
59
60/// Create the operations router with all ops endpoints.
61///
62/// This router provides REST access to all vibe-graph operations.
63/// Mount at `/api/ops` for full API access.
64pub fn create_ops_router(ctx: OpsContext) -> Router {
65    let state = Arc::new(OpsState { ctx });
66
67    let cors = CorsLayer::new()
68        .allow_origin(Any)
69        .allow_methods(Any)
70        .allow_headers(Any);
71
72    Router::new()
73        // Sync operations
74        .route("/sync", post(ops::sync_handler))
75        .route("/sync", get(ops::sync_query_handler))
76        // Graph operations
77        .route("/graph", post(ops::graph_handler))
78        .route("/graph", get(ops::graph_query_handler))
79        // Status
80        .route("/status", get(ops::status_handler))
81        // Load
82        .route("/load", get(ops::load_handler))
83        // Clean
84        .route("/clean", delete(ops::clean_handler))
85        // Git changes
86        .route("/git-changes", get(ops::git_changes_handler))
87        // Request tracing
88        .layer(
89            TraceLayer::new_for_http()
90                .make_span_with(
91                    DefaultMakeSpan::new()
92                        .level(Level::INFO)
93                        .include_headers(false),
94                )
95                .on_response(DefaultOnResponse::new().level(Level::INFO)),
96        )
97        .layer(cors)
98        .with_state(state)
99}
100
101/// Create a git commands router for a single-repo workspace.
102///
103/// This router provides REST access to git commands:
104/// - GET /repos - List available repositories
105/// - POST /add - Stage files
106/// - POST /commit - Create commit
107/// - POST /reset - Unstage files
108/// - GET /branches - List branches
109/// - POST /checkout - Switch branch
110/// - GET /log - Commit history
111/// - GET /diff - Get diff
112pub fn create_git_commands_router(workspace_path: PathBuf) -> Router {
113    let state = Arc::new(GitOpsState::single_repo(workspace_path));
114    build_git_commands_router(state)
115}
116
117/// Create a git commands router for a multi-repo workspace.
118///
119/// # Arguments
120///
121/// * `workspace_path` - The root workspace path
122/// * `repos` - List of (name, path) tuples for each repository
123pub fn create_git_commands_router_multi(
124    workspace_path: PathBuf,
125    repos: Vec<(String, PathBuf)>,
126) -> Router {
127    let state = Arc::new(GitOpsState::multi_repo(workspace_path, repos));
128    build_git_commands_router(state)
129}
130
131/// Create a file content router for serving workspace files.
132///
133/// Mount at `/file` for the syntax-highlighted viewer.
134pub fn create_file_router(workspace_path: PathBuf) -> Router {
135    let state = Arc::new(FileState {
136        workspace_root: workspace_path,
137    });
138
139    Router::new()
140        .route("/", get(file::file_handler))
141        .with_state(state)
142}
143
144/// Internal helper to build git commands router with given state.
145fn build_git_commands_router(state: Arc<GitOpsState>) -> Router {
146    let cors = CorsLayer::new()
147        .allow_origin(Any)
148        .allow_methods(Any)
149        .allow_headers(Any);
150
151    Router::new()
152        // List repos
153        .route("/repos", get(git::repos_handler))
154        // Stage files
155        .route("/add", post(git::add_handler))
156        // Commit
157        .route("/commit", post(git::commit_handler))
158        // Unstage files
159        .route("/reset", post(git::reset_handler))
160        // List branches
161        .route("/branches", get(git::branches_handler))
162        // Checkout branch
163        .route("/checkout", post(git::checkout_handler))
164        // Commit history
165        .route("/log", get(git::log_handler))
166        // Diff
167        .route("/diff", get(git::diff_handler))
168        .layer(
169            TraceLayer::new_for_http()
170                .make_span_with(
171                    DefaultMakeSpan::new()
172                        .level(Level::INFO)
173                        .include_headers(false),
174                )
175                .on_response(DefaultOnResponse::new().level(Level::INFO)),
176        )
177        .layer(cors)
178        .with_state(state)
179}
180
181/// Create a combined API router with both graph/ws and ops endpoints.
182///
183/// This creates a router that serves:
184/// - `/ops/*` - Operations API (sync, graph build, status, etc.)
185/// - `/health`, `/graph/*`, `/git/*`, `/ws` - Graph visualization API
186pub fn create_full_api_router(api_state: Arc<ApiState>, ops_ctx: OpsContext) -> Router {
187    let cors = CorsLayer::new()
188        .allow_origin(Any)
189        .allow_methods(Any)
190        .allow_headers(Any);
191
192    // Create the ops router (already has state applied, becomes Router<()>)
193    let ops_router = create_ops_router(ops_ctx);
194
195    // Create the visualization API router with its state
196    let viz_router = Router::new()
197        .route("/health", get(health::health_handler))
198        .route("/graph", get(graph::graph_handler))
199        .route("/graph/nodes", get(graph::nodes_handler))
200        .route("/graph/edges", get(graph::edges_handler))
201        .route("/graph/metadata", get(graph::metadata_handler))
202        .route("/git/changes", get(git::changes_handler))
203        .route("/ws", get(ws_handler))
204        .with_state(api_state);
205
206    // Merge both routers
207    Router::new()
208        .nest("/ops", ops_router)
209        .merge(viz_router)
210        .layer(
211            TraceLayer::new_for_http()
212                .make_span_with(
213                    DefaultMakeSpan::new()
214                        .level(Level::INFO)
215                        .include_headers(false),
216                )
217                .on_response(DefaultOnResponse::new().level(Level::INFO)),
218        )
219        .layer(cors)
220}
221
222/// Create a complete API router including git commands (single-repo).
223///
224/// This creates a router that serves:
225/// - `/ops/*` - Operations API (sync, graph build, status, etc.)
226/// - `/git/cmd/*` - Git command API (add, commit, reset, etc.)
227/// - `/health`, `/graph/*`, `/git/changes`, `/ws` - Graph visualization API
228pub fn create_full_api_router_with_git(
229    api_state: Arc<ApiState>,
230    ops_ctx: OpsContext,
231    workspace_path: PathBuf,
232) -> Router {
233    let git_cmd_router = create_git_commands_router(workspace_path.clone());
234    let file_router = create_file_router(workspace_path);
235    build_full_api_router_with_git_router(api_state, ops_ctx, git_cmd_router, file_router)
236}
237
238/// Create a complete API router including git commands (multi-repo).
239///
240/// This creates a router that serves:
241/// - `/ops/*` - Operations API (sync, graph build, status, etc.)
242/// - `/git/cmd/*` - Git command API with multi-repo support
243/// - `/file` - File content endpoint for syntax-highlighted viewer
244/// - `/health`, `/graph/*`, `/git/changes`, `/ws` - Graph visualization API
245pub fn create_full_api_router_with_git_multi(
246    api_state: Arc<ApiState>,
247    ops_ctx: OpsContext,
248    workspace_path: PathBuf,
249    repos: Vec<(String, PathBuf)>,
250) -> Router {
251    let git_cmd_router = create_git_commands_router_multi(workspace_path.clone(), repos);
252    let file_router = create_file_router(workspace_path);
253    build_full_api_router_with_git_router(api_state, ops_ctx, git_cmd_router, file_router)
254}
255
256/// Internal helper to build full router with git commands and file endpoint.
257fn build_full_api_router_with_git_router(
258    api_state: Arc<ApiState>,
259    ops_ctx: OpsContext,
260    git_cmd_router: Router,
261    file_router: Router,
262) -> Router {
263    let cors = CorsLayer::new()
264        .allow_origin(Any)
265        .allow_methods(Any)
266        .allow_headers(Any);
267
268    // Create the ops router
269    let ops_router = create_ops_router(ops_ctx);
270
271    // Create the visualization API router with its state
272    let viz_router = Router::new()
273        .route("/health", get(health::health_handler))
274        .route("/graph", get(graph::graph_handler))
275        .route("/graph/nodes", get(graph::nodes_handler))
276        .route("/graph/edges", get(graph::edges_handler))
277        .route("/graph/metadata", get(graph::metadata_handler))
278        .route("/git/changes", get(git::changes_handler))
279        .route("/ws", get(ws_handler))
280        .with_state(api_state);
281
282    // Merge all routers
283    Router::new()
284        .nest("/ops", ops_router)
285        .nest("/git/cmd", git_cmd_router)
286        .nest("/file", file_router)
287        .merge(viz_router)
288        .layer(
289            TraceLayer::new_for_http()
290                .make_span_with(
291                    DefaultMakeSpan::new()
292                        .level(Level::INFO)
293                        .include_headers(false),
294                )
295                .on_response(DefaultOnResponse::new().level(Level::INFO)),
296        )
297        .layer(cors)
298}