diff --git a/src/handler/http/models/folders.rs b/src/handler/http/models/folders.rs index e342296ae8..03d6e9be70 100644 --- a/src/handler/http/models/folders.rs +++ b/src/handler/http/models/folders.rs @@ -45,6 +45,14 @@ pub struct ListFoldersResponseBody { pub list: Vec, } +/// Indicates the type of data that the folder can contain. +#[derive(Clone, Debug, Deserialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum FolderType { + Dashboards, + Alerts, +} + /// Common folder fields used in HTTP request and response bodies. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] @@ -83,6 +91,15 @@ impl From for config::meta::folder::Folder { } } +impl From for infra::table::folders::FolderType { + fn from(value: FolderType) -> Self { + match value { + FolderType::Dashboards => Self::Dashboards, + FolderType::Alerts => Self::Alerts, + } + } +} + impl From> for ListFoldersResponseBody { fn from(value: Vec) -> Self { Self { diff --git a/src/handler/http/request/folders.rs b/src/handler/http/request/folders.rs index fbbd467e21..6e3d44404c 100644 --- a/src/handler/http/request/folders.rs +++ b/src/handler/http/request/folders.rs @@ -14,12 +14,11 @@ // along with this program. If not, see . use actix_web::{delete, get, post, put, web, HttpRequest, HttpResponse, Responder}; -use infra::table::folders::FolderType; use crate::{ common::meta::http::HttpResponse as MetaHttpResponse, handler::http::models::folders::{ - CreateFolderRequestBody, CreateFolderResponseBody, ListFoldersResponseBody, + CreateFolderRequestBody, CreateFolderResponseBody, FolderType, ListFoldersResponseBody, UpdateFolderRequestBody, }, service::folders::{self, FolderError}, @@ -58,6 +57,7 @@ impl From for HttpResponse { ), params( ("org_id" = String, Path, description = "Organization name"), + ("folder_type" = FolderType, Path, description = "Type of data the folder can contain"), ), request_body( content = CreateFolderRequestBody, @@ -72,15 +72,14 @@ impl From for HttpResponse { (status = StatusCode::INTERNAL_SERVER_ERROR, description = "Internal Server Error", body = HttpResponse), ), )] -#[post("/{org_id}/folders")] +#[post("/v2/{org_id}/folders/{folder_type}")] pub async fn create_folder( - path: web::Path, + path: web::Path<(String, FolderType)>, body: web::Json, ) -> impl Responder { - let org_id = path.into_inner(); + let (org_id, folder_type) = path.into_inner(); let folder = body.into_inner().into(); - let folder_type = FolderType::Dashboards; - match folders::save_folder(&org_id, folder, folder_type, false).await { + match folders::save_folder(&org_id, folder, folder_type.into(), false).await { Ok(folder) => { let body: CreateFolderResponseBody = folder.into(); HttpResponse::Ok().json(body) @@ -99,6 +98,7 @@ pub async fn create_folder( ), params( ("org_id" = String, Path, description = "Organization name"), + ("folder_type" = FolderType, Path, description = "Type of data the folder can contain"), ("folder_id" = String, Path, description = "Folder name"), ), request_body( @@ -114,15 +114,14 @@ pub async fn create_folder( (status = StatusCode::INTERNAL_SERVER_ERROR, description = "Internal Server Error", body = HttpResponse), ), )] -#[put("/{org_id}/folders/{folder_id}")] +#[put("/v2/{org_id}/folders/{folder_type}/{folder_id}")] pub async fn update_folder( - path: web::Path<(String, String)>, + path: web::Path<(String, FolderType, String)>, body: web::Json, ) -> impl Responder { - let (org_id, folder_id) = path.into_inner(); + let (org_id, folder_type, folder_id) = path.into_inner(); let folder = body.into_inner().into(); - let folder_type = FolderType::Dashboards; - match folders::update_folder(&org_id, &folder_id, folder_type, folder).await { + match folders::update_folder(&org_id, &folder_id, folder_type.into(), folder).await { Ok(_) => HttpResponse::Ok().body("Folder updated"), Err(err) => err.into(), } @@ -138,15 +137,19 @@ pub async fn update_folder( ), params( ("org_id" = String, Path, description = "Organization name"), + ("folder_type" = FolderType, Path, description = "Type of data the folder can contain"), ), responses( (status = StatusCode::OK, body = ListFoldersResponseBody), ), )] -#[get("/{org_id}/folders")] +#[get("/v2/{org_id}/folders/{folder_type}")] #[allow(unused_variables)] -pub async fn list_folders(path: web::Path, req: HttpRequest) -> impl Responder { - let org_id = path.into_inner(); +pub async fn list_folders( + path: web::Path<(String, FolderType)>, + req: HttpRequest, +) -> impl Responder { + let (org_id, folder_type) = path.into_inner(); #[cfg(not(feature = "enterprise"))] let user_id = None; @@ -157,7 +160,7 @@ pub async fn list_folders(path: web::Path, req: HttpRequest) -> impl Res }; let folder_type = FolderType::Dashboards; - match folders::list_folders(&org_id, user_id, folder_type).await { + match folders::list_folders(&org_id, user_id, folder_type.into()).await { Ok(folders) => { let body: ListFoldersResponseBody = folders.into(); HttpResponse::Ok().json(body) @@ -176,6 +179,7 @@ pub async fn list_folders(path: web::Path, req: HttpRequest) -> impl Res ), params( ("org_id" = String, Path, description = "Organization name"), + ("folder_type" = FolderType, Path, description = "Type of data the folder can contain"), ("folder_id" = String, Path, description = "Folder ID"), ), responses( @@ -183,11 +187,10 @@ pub async fn list_folders(path: web::Path, req: HttpRequest) -> impl Res (status = StatusCode::NOT_FOUND, description = "Folder not found", body = HttpResponse), ), )] -#[get("/{org_id}/folders/{folder_id}")] -pub async fn get_folder(path: web::Path<(String, String)>) -> impl Responder { - let (org_id, folder_id) = path.into_inner(); - let folder_type = FolderType::Dashboards; - match folders::get_folder(&org_id, &folder_id, folder_type).await { +#[get("/v2/{org_id}/folders/{folder_type}/{folder_id}")] +pub async fn get_folder(path: web::Path<(String, FolderType, String)>) -> impl Responder { + let (org_id, folder_type, folder_id) = path.into_inner(); + match folders::get_folder(&org_id, &folder_id, folder_type.into()).await { Ok(folder) => { let body: CreateFolderResponseBody = folder.into(); HttpResponse::Ok().json(body) @@ -206,6 +209,7 @@ pub async fn get_folder(path: web::Path<(String, String)>) -> impl Responder { ), params( ("org_id" = String, Path, description = "Organization name"), + ("folder_type" = FolderType, Path, description = "Type of data the folder can contain"), ("folder_id" = String, Path, description = "Folder ID"), ), responses( @@ -214,12 +218,197 @@ pub async fn get_folder(path: web::Path<(String, String)>) -> impl Responder { (status = StatusCode::INTERNAL_SERVER_ERROR, description = "Error", body = HttpResponse), ), )] -#[delete("/{org_id}/folders/{folder_id}")] -async fn delete_folder(path: web::Path<(String, String)>) -> impl Responder { - let (org_id, folder_id) = path.into_inner(); - let folder_type = FolderType::Dashboards; - match folders::delete_folder(&org_id, &folder_id, folder_type).await { +#[delete("/v2/{org_id}/folders/{folder_type}/{folder_id}")] +async fn delete_folder(path: web::Path<(String, FolderType, String)>) -> impl Responder { + let (org_id, folder_type, folder_id) = path.into_inner(); + match folders::delete_folder(&org_id, &folder_id, folder_type.into()).await { Ok(()) => HttpResponse::Ok().body("Folder deleted"), Err(err) => err.into(), } } + +/// Deprecated folder endpoints. +pub mod deprecated { + use super::*; + + /// CreateFolder + #[deprecated] + #[utoipa::path( + context_path = "/api", + tag = "Folders", + operation_id = "CreateFolder", + security( + ("Authorization" = []) + ), + params( + ("org_id" = String, Path, description = "Organization name"), + ), + request_body( + content = CreateFolderRequestBody, + description = "Folder details", + example = json!({ + "name": "Infrastructure", + "description": "Traffic patterns and network performance of the infrastructure", + }), + ), + responses( + (status = StatusCode::OK, description = "Folder created", body = CreateFolderResponseBody), + (status = StatusCode::INTERNAL_SERVER_ERROR, description = "Internal Server Error", body = HttpResponse), + ), + )] + #[post("/{org_id}/folders")] + pub async fn create_folder( + path: web::Path, + body: web::Json, + ) -> impl Responder { + let org_id = path.into_inner(); + let folder = body.into_inner().into(); + let folder_type = infra::table::folders::FolderType::Dashboards; + match folders::save_folder(&org_id, folder, folder_type, false).await { + Ok(folder) => { + let body: CreateFolderResponseBody = folder.into(); + HttpResponse::Ok().json(body) + } + Err(err) => err.into(), + } + } + + /// UpdateFolder + #[deprecated] + #[utoipa::path( + context_path = "/api", + tag = "Folders", + operation_id = "UpdateFolder", + security( + ("Authorization" = []) + ), + params( + ("org_id" = String, Path, description = "Organization name"), + ("folder_id" = String, Path, description = "Folder name"), + ), + request_body( + content = Folder, + description = "Folder details", + example = json!({ + "title": "Infra", + "description": "Traffic patterns and network performance of the infrastructure", + }), + ), + responses( + (status = StatusCode::OK, description = "Folder updated", body = HttpResponse), + (status = StatusCode::INTERNAL_SERVER_ERROR, description = "Internal Server Error", body = HttpResponse), + ), + )] + #[put("/{org_id}/folders/{folder_id}")] + pub async fn update_folder( + path: web::Path<(String, String)>, + body: web::Json, + ) -> impl Responder { + let (org_id, folder_id) = path.into_inner(); + let folder = body.into_inner().into(); + let folder_type = infra::table::folders::FolderType::Dashboards; + match folders::update_folder(&org_id, &folder_id, folder_type, folder).await { + Ok(_) => HttpResponse::Ok().body("Folder updated"), + Err(err) => err.into(), + } + } + + /// ListFolders + #[deprecated] + #[utoipa::path( + context_path = "/api", + tag = "Folders", + operation_id = "ListFolders", + security( + ("Authorization" = []) + ), + params( + ("org_id" = String, Path, description = "Organization name"), + ), + responses( + (status = StatusCode::OK, body = ListFoldersResponseBody), + ), + )] + #[get("/{org_id}/folders")] + #[allow(unused_variables)] + pub async fn list_folders(path: web::Path, req: HttpRequest) -> impl Responder { + let org_id = path.into_inner(); + + #[cfg(not(feature = "enterprise"))] + let user_id = None; + + #[cfg(feature = "enterprise")] + let Ok(user_id) = req.headers().get("user_id").map(|v| v.to_str()).transpose() else { + return HttpResponse::Forbidden().finish(); + }; + + let folder_type = infra::table::folders::FolderType::Dashboards; + match folders::list_folders(&org_id, user_id, folder_type).await { + Ok(folders) => { + let body: ListFoldersResponseBody = folders.into(); + HttpResponse::Ok().json(body) + } + Err(err) => err.into(), + } + } + + /// GetFolder + #[deprecated] + #[utoipa::path( + context_path = "/api", + tag = "Folders", + operation_id = "GetFolder", + security( + ("Authorization" = []) + ), + params( + ("org_id" = String, Path, description = "Organization name"), + ("folder_id" = String, Path, description = "Folder ID"), + ), + responses( + (status = StatusCode::OK, body = GetFolderResponseBody), + (status = StatusCode::NOT_FOUND, description = "Folder not found", body = HttpResponse), + ), + )] + #[get("/{org_id}/folders/{folder_id}")] + pub async fn get_folder(path: web::Path<(String, String)>) -> impl Responder { + let (org_id, folder_id) = path.into_inner(); + let folder_type = infra::table::folders::FolderType::Dashboards; + match folders::get_folder(&org_id, &folder_id, folder_type).await { + Ok(folder) => { + let body: CreateFolderResponseBody = folder.into(); + HttpResponse::Ok().json(body) + } + Err(err) => err.into(), + } + } + + /// DeleteFolder + #[deprecated] + #[utoipa::path( + context_path = "/api", + tag = "Folders", + operation_id = "DeleteFolder", + security( + ("Authorization" = []) + ), + params( + ("org_id" = String, Path, description = "Organization name"), + ("folder_id" = String, Path, description = "Folder ID"), + ), + responses( + (status = StatusCode::OK, description = "Success", body = HttpResponse), + (status = StatusCode::NOT_FOUND, description = "NotFound", body = HttpResponse), + (status = StatusCode::INTERNAL_SERVER_ERROR, description = "Error", body = HttpResponse), + ), + )] + #[delete("/{org_id}/folders/{folder_id}")] + async fn delete_folder(path: web::Path<(String, String)>) -> impl Responder { + let (org_id, folder_id) = path.into_inner(); + let folder_type = infra::table::folders::FolderType::Dashboards; + match folders::delete_folder(&org_id, &folder_id, folder_type).await { + Ok(()) => HttpResponse::Ok().body("Folder deleted"), + Err(err) => err.into(), + } + } +} diff --git a/src/handler/http/request/mod.rs b/src/handler/http/request/mod.rs index 6982de31da..3ec7d76730 100644 --- a/src/handler/http/request/mod.rs +++ b/src/handler/http/request/mod.rs @@ -18,6 +18,7 @@ pub mod authz; pub mod clusters; pub mod dashboards; pub mod enrichment_table; +#[allow(deprecated)] pub mod folders; pub mod functions; pub mod kv; diff --git a/src/handler/http/router/mod.rs b/src/handler/http/router/mod.rs index 1ebf2afbbc..097a13de2c 100644 --- a/src/handler/http/router/mod.rs +++ b/src/handler/http/router/mod.rs @@ -403,11 +403,6 @@ pub fn get_service_routes(cfg: &mut web::ServiceConfig) { .service(search::saved_view::get_view) .service(search::saved_view::get_views) .service(search::saved_view::delete_view) - .service(folders::create_folder) - .service(folders::list_folders) - .service(folders::update_folder) - .service(folders::get_folder) - .service(folders::delete_folder) .service(functions::save_function) .service(functions::list_functions) .service(functions::delete_function) @@ -426,6 +421,16 @@ pub fn get_service_routes(cfg: &mut web::ServiceConfig) { .service(dashboards::reports::delete_report) .service(dashboards::reports::enable_report) .service(dashboards::reports::trigger_report) + .service(folders::create_folder) + .service(folders::list_folders) + .service(folders::update_folder) + .service(folders::get_folder) + .service(folders::delete_folder) + .service(folders::deprecated::create_folder) + .service(folders::deprecated::list_folders) + .service(folders::deprecated::update_folder) + .service(folders::deprecated::get_folder) + .service(folders::deprecated::delete_folder) .service(alerts::alert::save_alert) .service(alerts::alert::update_alert) .service(alerts::alert::get_alert) diff --git a/src/handler/http/router/openapi.rs b/src/handler/http/router/openapi.rs index dca7982c04..942da58148 100644 --- a/src/handler/http/router/openapi.rs +++ b/src/handler/http/router/openapi.rs @@ -75,6 +75,11 @@ use crate::{common::meta, handler::http::request}; request::folders::list_folders, request::folders::get_folder, request::folders::update_folder, + request::folders::deprecated::delete_folder, + request::folders::deprecated::create_folder, + request::folders::deprecated::list_folders, + request::folders::deprecated::get_folder, + request::folders::deprecated::update_folder, request::functions::list_functions, request::functions::update_function, request::functions::save_function, @@ -171,6 +176,7 @@ use crate::{common::meta, handler::http::request}; crate::handler::http::models::folders::GetFolderResponseBody, crate::handler::http::models::folders::ListFoldersResponseBody, crate::handler::http::models::folders::UpdateFolderRequestBody, + crate::handler::http::models::folders::FolderType, config::meta::function::Transform, config::meta::function::FunctionList, config::meta::function::StreamOrder,