//! `/api/level-up-proposals/*` + trigger endpoints — Slice 8.5. use axum::extract::{Path, State}; use axum::response::Json; use serde::Deserialize; use uuid::Uuid; use cm_db::repo::level_up::LevelUpProposal; use crate::{ApiError, AppState, Authed}; pub async fn list_pending( State(state): State, Authed(user): Authed, ) -> Result>, ApiError> { let rows = cm_db::repo::level_up::list_pending(&state.pool, user.workspace_id.as_uuid()).await?; Ok(Json(rows)) } pub async fn get_proposal( State(state): State, Authed(user): Authed, Path(id): Path, ) -> Result, ApiError> { let p = cm_db::repo::level_up::get(&state.pool, id, user.workspace_id.as_uuid()) .await? .ok_or(ApiError::NotFound)?; Ok(Json(p)) } /// POST /api/claws/{id}/level-up — LLM proposes agent improvements. /// Returns the new proposal id; reviewer approves via the apply route. pub async fn propose_for_agent( State(state): State, Authed(user): Authed, Path(agent_id): Path, ) -> Result, ApiError> { let id = crate::level_up::propose_agent(&state.pool, &state.runtime, user.workspace_id, user.user_id, agent_id) .await .map_err(|e| { eprintln!("level_up: propose_agent {agent_id} failed: {e}"); ApiError::Internal })?; Ok(Json(serde_json::json!({ "proposal_id": id }))) } /// POST /api/teams/{id}/level-up — LLM proposes team improvements. pub async fn propose_for_team( State(state): State, Authed(user): Authed, Path(team_id): Path, ) -> Result, ApiError> { let id = crate::level_up::propose_team(&state.pool, &state.runtime, user.workspace_id, user.user_id, team_id) .await .map_err(|e| { eprintln!("level_up: propose_team {team_id} failed: {e}"); ApiError::Internal })?; Ok(Json(serde_json::json!({ "proposal_id": id }))) } #[derive(Debug, Deserialize)] pub struct ApplyRequest { /// Ids from payload.suggested_items[] that the reviewer approved. pub approved_item_ids: Vec, } pub async fn apply_proposal( State(state): State, Authed(user): Authed, Path(id): Path, Json(body): Json, ) -> Result, ApiError> { crate::level_up::apply( &state.pool, user.workspace_id, user.user_id, id, &body.approved_item_ids, ) .await .map_err(|e| { eprintln!("level_up::apply {id} failed: {e}"); ApiError::Internal })?; let p = cm_db::repo::level_up::get(&state.pool, id, user.workspace_id.as_uuid()) .await? .ok_or(ApiError::NotFound)?; Ok(Json(p)) } pub async fn reject_proposal( State(state): State, Authed(user): Authed, Path(id): Path, ) -> Result, ApiError> { cm_db::repo::level_up::mark_rejected( &state.pool, id, user.workspace_id.as_uuid(), user.user_id.as_uuid(), ) .await?; let p = cm_db::repo::level_up::get(&state.pool, id, user.workspace_id.as_uuid()) .await? .ok_or(ApiError::NotFound)?; Ok(Json(p)) }