Refactored and documented the HTTP API
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
use crate::http::context::{AppContext, OpenApiResult, bad_request};
|
use crate::http::context::{AppContext, OpenApiResult, bad_request};
|
||||||
use crate::llm::chat;
|
use crate::llm::chat;
|
||||||
use poem_openapi::{Object, OpenApi, payload::Json};
|
use poem_openapi::{Object, OpenApi, Tags, payload::Json};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -9,6 +9,11 @@ struct ApiKeyPayload {
|
|||||||
api_key: String,
|
api_key: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Tags)]
|
||||||
|
enum AnthropicTags {
|
||||||
|
Anthropic,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct AnthropicApi {
|
pub struct AnthropicApi {
|
||||||
ctx: Arc<AppContext>,
|
ctx: Arc<AppContext>,
|
||||||
}
|
}
|
||||||
@@ -19,8 +24,11 @@ impl AnthropicApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[OpenApi]
|
#[OpenApi(tag = "AnthropicTags::Anthropic")]
|
||||||
impl AnthropicApi {
|
impl AnthropicApi {
|
||||||
|
/// Check whether an Anthropic API key is stored.
|
||||||
|
///
|
||||||
|
/// Returns `true` if a non-empty key is present, otherwise `false`.
|
||||||
#[oai(path = "/anthropic/key/exists", method = "get")]
|
#[oai(path = "/anthropic/key/exists", method = "get")]
|
||||||
async fn get_anthropic_api_key_exists(&self) -> OpenApiResult<Json<bool>> {
|
async fn get_anthropic_api_key_exists(&self) -> OpenApiResult<Json<bool>> {
|
||||||
let exists =
|
let exists =
|
||||||
@@ -28,6 +36,9 @@ impl AnthropicApi {
|
|||||||
Ok(Json(exists))
|
Ok(Json(exists))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Store or update the Anthropic API key used for requests.
|
||||||
|
///
|
||||||
|
/// Returns `true` when the key is saved successfully.
|
||||||
#[oai(path = "/anthropic/key", method = "post")]
|
#[oai(path = "/anthropic/key", method = "post")]
|
||||||
async fn set_anthropic_api_key(
|
async fn set_anthropic_api_key(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -1,14 +1,22 @@
|
|||||||
use crate::http::context::{AppContext, OpenApiResult, bad_request};
|
use crate::http::context::{AppContext, OpenApiResult, bad_request};
|
||||||
use crate::llm::chat;
|
use crate::llm::chat;
|
||||||
use poem_openapi::{OpenApi, payload::Json};
|
use poem_openapi::{OpenApi, Tags, payload::Json};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Tags)]
|
||||||
|
enum ChatTags {
|
||||||
|
Chat,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ChatApi {
|
pub struct ChatApi {
|
||||||
pub ctx: Arc<AppContext>,
|
pub ctx: Arc<AppContext>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[OpenApi]
|
#[OpenApi(tag = "ChatTags::Chat")]
|
||||||
impl ChatApi {
|
impl ChatApi {
|
||||||
|
/// Cancel the currently running chat stream, if any.
|
||||||
|
///
|
||||||
|
/// Returns `true` once the cancellation signal is issued.
|
||||||
#[oai(path = "/chat/cancel", method = "post")]
|
#[oai(path = "/chat/cancel", method = "post")]
|
||||||
async fn cancel_chat(&self) -> OpenApiResult<Json<bool>> {
|
async fn cancel_chat(&self) -> OpenApiResult<Json<bool>> {
|
||||||
chat::cancel_chat(&self.ctx.state).map_err(bad_request)?;
|
chat::cancel_chat(&self.ctx.state).map_err(bad_request)?;
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
use crate::http::context::{AppContext, OpenApiResult, bad_request};
|
|
||||||
use crate::io::fs;
|
|
||||||
use poem_openapi::{Object, OpenApi, payload::Json};
|
|
||||||
use serde::Deserialize;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Object)]
|
|
||||||
struct FilePathPayload {
|
|
||||||
pub path: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Object)]
|
|
||||||
struct WriteFilePayload {
|
|
||||||
pub path: String,
|
|
||||||
pub content: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct FsApi {
|
|
||||||
pub ctx: Arc<AppContext>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[OpenApi]
|
|
||||||
impl FsApi {
|
|
||||||
#[oai(path = "/fs/read", method = "post")]
|
|
||||||
async fn read_file(&self, payload: Json<FilePathPayload>) -> OpenApiResult<Json<String>> {
|
|
||||||
let content = fs::read_file(payload.0.path, &self.ctx.state)
|
|
||||||
.await
|
|
||||||
.map_err(bad_request)?;
|
|
||||||
Ok(Json(content))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[oai(path = "/fs/write", method = "post")]
|
|
||||||
async fn write_file(&self, payload: Json<WriteFilePayload>) -> OpenApiResult<Json<bool>> {
|
|
||||||
fs::write_file(payload.0.path, payload.0.content, &self.ctx.state)
|
|
||||||
.await
|
|
||||||
.map_err(bad_request)?;
|
|
||||||
Ok(Json(true))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[oai(path = "/fs/list", method = "post")]
|
|
||||||
async fn list_directory(
|
|
||||||
&self,
|
|
||||||
payload: Json<FilePathPayload>,
|
|
||||||
) -> OpenApiResult<Json<Vec<fs::FileEntry>>> {
|
|
||||||
let entries = fs::list_directory(payload.0.path, &self.ctx.state)
|
|
||||||
.await
|
|
||||||
.map_err(bad_request)?;
|
|
||||||
Ok(Json(entries))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
94
server/src/http/io.rs
Normal file
94
server/src/http/io.rs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
use crate::http::context::{AppContext, OpenApiResult, bad_request};
|
||||||
|
use crate::io::fs as io_fs;
|
||||||
|
use poem_openapi::{Object, OpenApi, Tags, payload::Json};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Tags)]
|
||||||
|
enum IoTags {
|
||||||
|
Io,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Object)]
|
||||||
|
struct FilePathPayload {
|
||||||
|
pub path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Object)]
|
||||||
|
struct WriteFilePayload {
|
||||||
|
pub path: String,
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Object)]
|
||||||
|
struct SearchPayload {
|
||||||
|
query: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Object)]
|
||||||
|
struct ExecShellPayload {
|
||||||
|
pub command: String,
|
||||||
|
pub args: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct IoApi {
|
||||||
|
pub ctx: Arc<AppContext>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[OpenApi(tag = "IoTags::Io")]
|
||||||
|
impl IoApi {
|
||||||
|
/// Read a file from the currently open project and return its contents.
|
||||||
|
#[oai(path = "/io/fs/read", method = "post")]
|
||||||
|
async fn read_file(&self, payload: Json<FilePathPayload>) -> OpenApiResult<Json<String>> {
|
||||||
|
let content = io_fs::read_file(payload.0.path, &self.ctx.state)
|
||||||
|
.await
|
||||||
|
.map_err(bad_request)?;
|
||||||
|
Ok(Json(content))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a file to the currently open project, creating parent directories if needed.
|
||||||
|
#[oai(path = "/io/fs/write", method = "post")]
|
||||||
|
async fn write_file(&self, payload: Json<WriteFilePayload>) -> OpenApiResult<Json<bool>> {
|
||||||
|
io_fs::write_file(payload.0.path, payload.0.content, &self.ctx.state)
|
||||||
|
.await
|
||||||
|
.map_err(bad_request)?;
|
||||||
|
Ok(Json(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List files and folders in a directory within the currently open project.
|
||||||
|
#[oai(path = "/io/fs/list", method = "post")]
|
||||||
|
async fn list_directory(
|
||||||
|
&self,
|
||||||
|
payload: Json<FilePathPayload>,
|
||||||
|
) -> OpenApiResult<Json<Vec<io_fs::FileEntry>>> {
|
||||||
|
let entries = io_fs::list_directory(payload.0.path, &self.ctx.state)
|
||||||
|
.await
|
||||||
|
.map_err(bad_request)?;
|
||||||
|
Ok(Json(entries))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Search the currently open project for files containing the provided query string.
|
||||||
|
#[oai(path = "/io/search", method = "post")]
|
||||||
|
async fn search_files(
|
||||||
|
&self,
|
||||||
|
payload: Json<SearchPayload>,
|
||||||
|
) -> OpenApiResult<Json<Vec<crate::io::search::SearchResult>>> {
|
||||||
|
let results = crate::io::search::search_files(payload.0.query, &self.ctx.state)
|
||||||
|
.await
|
||||||
|
.map_err(bad_request)?;
|
||||||
|
Ok(Json(results))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute an allowlisted shell command in the currently open project.
|
||||||
|
#[oai(path = "/io/shell/exec", method = "post")]
|
||||||
|
async fn exec_shell(
|
||||||
|
&self,
|
||||||
|
payload: Json<ExecShellPayload>,
|
||||||
|
) -> OpenApiResult<Json<crate::io::shell::CommandOutput>> {
|
||||||
|
let output =
|
||||||
|
crate::io::shell::exec_shell(payload.0.command, payload.0.args, &self.ctx.state)
|
||||||
|
.await
|
||||||
|
.map_err(bad_request)?;
|
||||||
|
Ok(Json(output))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,26 +2,22 @@ pub mod anthropic;
|
|||||||
pub mod assets;
|
pub mod assets;
|
||||||
pub mod chat;
|
pub mod chat;
|
||||||
pub mod context;
|
pub mod context;
|
||||||
pub mod fs;
|
|
||||||
pub mod health;
|
pub mod health;
|
||||||
|
pub mod io;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
|
|
||||||
pub mod project;
|
pub mod project;
|
||||||
pub mod search;
|
|
||||||
pub mod shell;
|
|
||||||
pub mod ws;
|
pub mod ws;
|
||||||
|
|
||||||
use anthropic::AnthropicApi;
|
use anthropic::AnthropicApi;
|
||||||
use chat::ChatApi;
|
use chat::ChatApi;
|
||||||
use context::AppContext;
|
use context::AppContext;
|
||||||
use fs::FsApi;
|
use io::IoApi;
|
||||||
use model::ModelApi;
|
use model::ModelApi;
|
||||||
use poem::EndpointExt;
|
use poem::EndpointExt;
|
||||||
use poem::{Route, get};
|
use poem::{Route, get};
|
||||||
use poem_openapi::OpenApiService;
|
use poem_openapi::OpenApiService;
|
||||||
use project::ProjectApi;
|
use project::ProjectApi;
|
||||||
use search::SearchApi;
|
|
||||||
use shell::ShellApi;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub fn build_routes(ctx: AppContext) -> impl poem::Endpoint {
|
pub fn build_routes(ctx: AppContext) -> impl poem::Endpoint {
|
||||||
@@ -40,26 +36,17 @@ pub fn build_routes(ctx: AppContext) -> impl poem::Endpoint {
|
|||||||
.data(ctx_arc)
|
.data(ctx_arc)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApiTuple = (
|
type ApiTuple = (ProjectApi, ModelApi, AnthropicApi, IoApi, ChatApi);
|
||||||
ProjectApi,
|
|
||||||
ModelApi,
|
|
||||||
AnthropicApi,
|
|
||||||
FsApi,
|
|
||||||
SearchApi,
|
|
||||||
ShellApi,
|
|
||||||
ChatApi,
|
|
||||||
);
|
|
||||||
|
|
||||||
type ApiService = OpenApiService<ApiTuple, ()>;
|
type ApiService = OpenApiService<ApiTuple, ()>;
|
||||||
|
|
||||||
|
/// All HTTP methods are documented by OpenAPI at /docs
|
||||||
pub fn build_openapi_service(ctx: Arc<AppContext>) -> (ApiService, ApiService) {
|
pub fn build_openapi_service(ctx: Arc<AppContext>) -> (ApiService, ApiService) {
|
||||||
let api = (
|
let api = (
|
||||||
ProjectApi { ctx: ctx.clone() },
|
ProjectApi { ctx: ctx.clone() },
|
||||||
ModelApi { ctx: ctx.clone() },
|
ModelApi { ctx: ctx.clone() },
|
||||||
AnthropicApi::new(ctx.clone()),
|
AnthropicApi::new(ctx.clone()),
|
||||||
FsApi { ctx: ctx.clone() },
|
IoApi { ctx: ctx.clone() },
|
||||||
SearchApi { ctx: ctx.clone() },
|
|
||||||
ShellApi { ctx: ctx.clone() },
|
|
||||||
ChatApi { ctx: ctx.clone() },
|
ChatApi { ctx: ctx.clone() },
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -70,9 +57,7 @@ pub fn build_openapi_service(ctx: Arc<AppContext>) -> (ApiService, ApiService) {
|
|||||||
ProjectApi { ctx: ctx.clone() },
|
ProjectApi { ctx: ctx.clone() },
|
||||||
ModelApi { ctx: ctx.clone() },
|
ModelApi { ctx: ctx.clone() },
|
||||||
AnthropicApi::new(ctx.clone()),
|
AnthropicApi::new(ctx.clone()),
|
||||||
FsApi { ctx: ctx.clone() },
|
IoApi { ctx: ctx.clone() },
|
||||||
SearchApi { ctx: ctx.clone() },
|
|
||||||
ShellApi { ctx: ctx.clone() },
|
|
||||||
ChatApi { ctx },
|
ChatApi { ctx },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
use crate::http::context::{AppContext, OpenApiResult, bad_request};
|
use crate::http::context::{AppContext, OpenApiResult, bad_request};
|
||||||
use crate::io::fs;
|
use crate::io::fs;
|
||||||
use crate::llm::chat;
|
use crate::llm::chat;
|
||||||
use poem_openapi::{Object, OpenApi, param::Query, payload::Json};
|
use poem_openapi::{Object, OpenApi, Tags, param::Query, payload::Json};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Tags)]
|
||||||
|
enum ModelTags {
|
||||||
|
Model,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Object)]
|
#[derive(Deserialize, Object)]
|
||||||
struct ModelPayload {
|
struct ModelPayload {
|
||||||
model: String,
|
model: String,
|
||||||
@@ -14,20 +19,24 @@ pub struct ModelApi {
|
|||||||
pub ctx: Arc<AppContext>,
|
pub ctx: Arc<AppContext>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[OpenApi]
|
#[OpenApi(tag = "ModelTags::Model")]
|
||||||
impl ModelApi {
|
impl ModelApi {
|
||||||
|
/// Get the currently selected model preference, if any.
|
||||||
#[oai(path = "/model", method = "get")]
|
#[oai(path = "/model", method = "get")]
|
||||||
async fn get_model_preference(&self) -> OpenApiResult<Json<Option<String>>> {
|
async fn get_model_preference(&self) -> OpenApiResult<Json<Option<String>>> {
|
||||||
let result = fs::get_model_preference(self.ctx.store.as_ref()).map_err(bad_request)?;
|
let result = fs::get_model_preference(self.ctx.store.as_ref()).map_err(bad_request)?;
|
||||||
Ok(Json(result))
|
Ok(Json(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Persist the selected model preference.
|
||||||
#[oai(path = "/model", method = "post")]
|
#[oai(path = "/model", method = "post")]
|
||||||
async fn set_model_preference(&self, payload: Json<ModelPayload>) -> OpenApiResult<Json<bool>> {
|
async fn set_model_preference(&self, payload: Json<ModelPayload>) -> OpenApiResult<Json<bool>> {
|
||||||
fs::set_model_preference(payload.0.model, self.ctx.store.as_ref()).map_err(bad_request)?;
|
fs::set_model_preference(payload.0.model, self.ctx.store.as_ref()).map_err(bad_request)?;
|
||||||
Ok(Json(true))
|
Ok(Json(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetch available model names from an Ollama server.
|
||||||
|
/// Optionally override the base URL via query string.
|
||||||
#[oai(path = "/ollama/models", method = "get")]
|
#[oai(path = "/ollama/models", method = "get")]
|
||||||
async fn get_ollama_models(
|
async fn get_ollama_models(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
use crate::http::context::{AppContext, OpenApiResult, bad_request};
|
use crate::http::context::{AppContext, OpenApiResult, bad_request};
|
||||||
use crate::io::fs;
|
use crate::io::fs;
|
||||||
use poem_openapi::{Object, OpenApi, payload::Json};
|
use poem_openapi::{Object, OpenApi, Tags, payload::Json};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Tags)]
|
||||||
|
enum ProjectTags {
|
||||||
|
Project,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Object)]
|
#[derive(Deserialize, Object)]
|
||||||
struct PathPayload {
|
struct PathPayload {
|
||||||
path: String,
|
path: String,
|
||||||
@@ -13,8 +18,11 @@ pub struct ProjectApi {
|
|||||||
pub ctx: Arc<AppContext>,
|
pub ctx: Arc<AppContext>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[OpenApi]
|
#[OpenApi(tag = "ProjectTags::Project")]
|
||||||
impl ProjectApi {
|
impl ProjectApi {
|
||||||
|
/// Get the currently open project path (if any).
|
||||||
|
///
|
||||||
|
/// Returns null when no project is open.
|
||||||
#[oai(path = "/project", method = "get")]
|
#[oai(path = "/project", method = "get")]
|
||||||
async fn get_current_project(&self) -> OpenApiResult<Json<Option<String>>> {
|
async fn get_current_project(&self) -> OpenApiResult<Json<Option<String>>> {
|
||||||
let result = fs::get_current_project(&self.ctx.state, self.ctx.store.as_ref())
|
let result = fs::get_current_project(&self.ctx.state, self.ctx.store.as_ref())
|
||||||
@@ -22,6 +30,9 @@ impl ProjectApi {
|
|||||||
Ok(Json(result))
|
Ok(Json(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Open a project and set it as the current project.
|
||||||
|
///
|
||||||
|
/// Persists the selected path for later sessions.
|
||||||
#[oai(path = "/project", method = "post")]
|
#[oai(path = "/project", method = "post")]
|
||||||
async fn open_project(&self, payload: Json<PathPayload>) -> OpenApiResult<Json<String>> {
|
async fn open_project(&self, payload: Json<PathPayload>) -> OpenApiResult<Json<String>> {
|
||||||
let confirmed = fs::open_project(payload.0.path, &self.ctx.state, self.ctx.store.as_ref())
|
let confirmed = fs::open_project(payload.0.path, &self.ctx.state, self.ctx.store.as_ref())
|
||||||
@@ -30,6 +41,7 @@ impl ProjectApi {
|
|||||||
Ok(Json(confirmed))
|
Ok(Json(confirmed))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Close the current project and clear the stored selection.
|
||||||
#[oai(path = "/project", method = "delete")]
|
#[oai(path = "/project", method = "delete")]
|
||||||
async fn close_project(&self) -> OpenApiResult<Json<bool>> {
|
async fn close_project(&self) -> OpenApiResult<Json<bool>> {
|
||||||
fs::close_project(&self.ctx.state, self.ctx.store.as_ref()).map_err(bad_request)?;
|
fs::close_project(&self.ctx.state, self.ctx.store.as_ref()).map_err(bad_request)?;
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
use crate::http::context::{AppContext, OpenApiResult, bad_request};
|
|
||||||
use poem_openapi::{Object, OpenApi, payload::Json};
|
|
||||||
use serde::Deserialize;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub struct SearchApi {
|
|
||||||
pub ctx: Arc<AppContext>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Object)]
|
|
||||||
struct SearchPayload {
|
|
||||||
query: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[OpenApi]
|
|
||||||
impl SearchApi {
|
|
||||||
#[oai(path = "/fs/search", method = "post")]
|
|
||||||
async fn search_files(
|
|
||||||
&self,
|
|
||||||
payload: Json<SearchPayload>,
|
|
||||||
) -> OpenApiResult<Json<Vec<crate::io::search::SearchResult>>> {
|
|
||||||
let ctx = self.ctx.clone();
|
|
||||||
let results = crate::io::search::search_files(payload.0.query, &ctx.state)
|
|
||||||
.await
|
|
||||||
.map_err(bad_request)?;
|
|
||||||
Ok(Json(results))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
use crate::http::context::{AppContext, OpenApiResult, bad_request};
|
|
||||||
use poem_openapi::{Object, OpenApi, payload::Json};
|
|
||||||
use serde::Deserialize;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Object)]
|
|
||||||
struct ExecShellPayload {
|
|
||||||
pub command: String,
|
|
||||||
pub args: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ShellApi {
|
|
||||||
pub ctx: Arc<AppContext>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[OpenApi]
|
|
||||||
impl ShellApi {
|
|
||||||
#[oai(path = "/shell/exec", method = "post")]
|
|
||||||
async fn exec_shell(
|
|
||||||
&self,
|
|
||||||
payload: Json<ExecShellPayload>,
|
|
||||||
) -> OpenApiResult<Json<crate::io::shell::CommandOutput>> {
|
|
||||||
let output =
|
|
||||||
crate::io::shell::exec_shell(payload.0.command, payload.0.args, &self.ctx.state)
|
|
||||||
.await
|
|
||||||
.map_err(bad_request)?;
|
|
||||||
Ok(Json(output))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user