use std::sync::Arc; use axum::{extract::Extension, Json}; use serde::Deserialize; use crate::app_error::AppCommandError; use crate::app_state::AppState; use crate::db::service::folder_command_service; use crate::models::*; // --------------------------------------------------------------------------- // Param structs // --------------------------------------------------------------------------- #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct FolderIdParams { pub folder_id: i32, } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct CreateFolderCommandParams { pub folder_id: i32, pub name: String, pub command: String, } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct UpdateFolderCommandParams { pub id: i32, pub name: Option, pub command: Option, pub sort_order: Option, } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct DeleteFolderCommandParams { pub id: i32, } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct ReorderFolderCommandsParams { pub folder_id: i32, pub ids: Vec, } // --------------------------------------------------------------------------- // Handlers // --------------------------------------------------------------------------- pub async fn list_folder_commands( Extension(state): Extension>, Json(params): Json, ) -> Result>, AppCommandError> { let db = &state.db; let result = folder_command_service::list_by_folder(&db.conn, params.folder_id) .await .map_err(AppCommandError::from)?; Ok(Json(result)) } pub async fn create_folder_command( Extension(state): Extension>, Json(params): Json, ) -> Result, AppCommandError> { let db = &state.db; let result = folder_command_service::create(&db.conn, params.folder_id, ¶ms.name, ¶ms.command) .await .map_err(AppCommandError::from)?; Ok(Json(result)) } pub async fn update_folder_command( Extension(state): Extension>, Json(params): Json, ) -> Result, AppCommandError> { let db = &state.db; let result = folder_command_service::update( &db.conn, params.id, params.name, params.command, params.sort_order, ) .await .map_err(AppCommandError::from)?; Ok(Json(result)) } pub async fn delete_folder_command( Extension(state): Extension>, Json(params): Json, ) -> Result, AppCommandError> { let db = &state.db; folder_command_service::delete(&db.conn, params.id) .await .map_err(AppCommandError::from)?; Ok(Json(())) } pub async fn reorder_folder_commands( Extension(state): Extension>, Json(params): Json, ) -> Result, AppCommandError> { let db = &state.db; folder_command_service::reorder(&db.conn, params.folder_id, params.ids) .await .map_err(AppCommandError::from)?; Ok(Json(())) } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct BootstrapFolderCommandsParams { pub folder_id: i32, pub folder_path: String, } pub async fn bootstrap_folder_commands_from_package_json( Extension(state): Extension>, Json(params): Json, ) -> Result>, AppCommandError> { let db = &state.db; let existing = folder_command_service::list_by_folder(&db.conn, params.folder_id) .await .map_err(AppCommandError::from)?; if !existing.is_empty() { return Ok(Json(existing)); } let commands_to_create = tokio::task::spawn_blocking(move || { crate::commands::folder_commands::load_package_scripts_as_commands(¶ms.folder_path) }) .await .map_err(|e| { AppCommandError::new( crate::app_error::AppErrorCode::TaskExecutionFailed, format!("bootstrap task failed: {e}"), ) })?; if commands_to_create.is_empty() { return Ok(Json(existing)); } folder_command_service::create_many(&db.conn, params.folder_id, &commands_to_create) .await .map_err(AppCommandError::from)?; let result = folder_command_service::list_by_folder(&db.conn, params.folder_id) .await .map_err(AppCommandError::from)?; Ok(Json(result)) }