// Projects API endpoints use crate::api::ApiResult; use axum::{ extract::{Path, State}, http::StatusCode, response::IntoResponse, Json, }; use vapora_shared::models::Project; use crate::api::state::AppState; /// List all projects for a tenant /// /// GET /api/v1/projects pub async fn list_projects(State(state): State) -> ApiResult { // TODO: Extract tenant_id from JWT token let tenant_id = "default"; let projects = state.project_service.list_projects(tenant_id).await?; Ok(Json(projects)) } /// Get a specific project /// /// GET /api/v1/projects/:id pub async fn get_project( State(state): State, Path(id): Path, ) -> ApiResult { // TODO: Extract tenant_id from JWT token let tenant_id = "default"; let project = state.project_service.get_project(&id, tenant_id).await?; Ok(Json(project)) } /// Create a new project /// /// POST /api/v1/projects pub async fn create_project( State(state): State, Json(mut project): Json, ) -> ApiResult { // TODO: Extract tenant_id from JWT token project.tenant_id = "default".to_string(); let created = state.project_service.create_project(project).await?; Ok((StatusCode::CREATED, Json(created))) } /// Update a project /// /// PUT /api/v1/projects/:id pub async fn update_project( State(state): State, Path(id): Path, Json(updates): Json, ) -> ApiResult { // TODO: Extract tenant_id from JWT token let tenant_id = "default"; let updated = state .project_service .update_project(&id, tenant_id, updates) .await?; Ok(Json(updated)) } /// Delete a project /// /// DELETE /api/v1/projects/:id pub async fn delete_project( State(state): State, Path(id): Path, ) -> ApiResult { // TODO: Extract tenant_id from JWT token let tenant_id = "default"; state.project_service.delete_project(&id, tenant_id).await?; Ok(StatusCode::NO_CONTENT) } /// Add a feature to a project /// /// POST /api/v1/projects/:id/features pub async fn add_feature( State(state): State, Path(id): Path, Json(payload): Json, ) -> ApiResult { // TODO: Extract tenant_id from JWT token let tenant_id = "default"; let feature = payload["feature"] .as_str() .ok_or_else(|| { vapora_shared::VaporaError::InvalidInput("Missing 'feature' field".to_string()) })? .to_string(); let updated = state .project_service .add_feature(&id, tenant_id, feature) .await?; Ok(Json(updated)) } /// Remove a feature from a project /// /// DELETE /api/v1/projects/:id/features/:feature pub async fn remove_feature( State(state): State, Path((id, feature)): Path<(String, String)>, ) -> ApiResult { // TODO: Extract tenant_id from JWT token let tenant_id = "default"; let updated = state .project_service .remove_feature(&id, tenant_id, &feature) .await?; Ok(Json(updated)) } /// Archive a project /// /// POST /api/v1/projects/:id/archive pub async fn archive_project( State(state): State, Path(id): Path, ) -> ApiResult { // TODO: Extract tenant_id from JWT token let tenant_id = "default"; let updated = state .project_service .archive_project(&id, tenant_id) .await?; Ok(Json(updated)) }