From 143a866b48394bb3382ac0aa37882fb7a10be699 Mon Sep 17 00:00:00 2001 From: Tamo Date: Tue, 8 Oct 2024 17:05:06 +0200 Subject: [PATCH] implements more routes --- meilisearch/src/routes/dump.rs | 50 +++++++++++ meilisearch/src/routes/indexes/mod.rs | 5 +- meilisearch/src/routes/logs.rs | 121 +++++++++++++++++++++++++- meilisearch/src/routes/metrics.rs | 91 +++++++++++++++++++ meilisearch/src/routes/mod.rs | 57 +++++++++++- meilisearch/src/routes/snapshot.rs | 45 ++++++++++ meilisearch/src/routes/tasks.rs | 23 ----- 7 files changed, 360 insertions(+), 32 deletions(-) diff --git a/meilisearch/src/routes/dump.rs b/meilisearch/src/routes/dump.rs index 7f3cd06a5..d7ba92191 100644 --- a/meilisearch/src/routes/dump.rs +++ b/meilisearch/src/routes/dump.rs @@ -6,6 +6,7 @@ use meilisearch_types::error::ResponseError; use meilisearch_types::tasks::KindWithContent; use serde_json::json; use tracing::debug; +use utoipa::OpenApi; use crate::analytics::Analytics; use crate::extractors::authentication::policies::*; @@ -14,10 +15,59 @@ use crate::extractors::sequential_extractor::SeqHandler; use crate::routes::{get_task_id, is_dry_run, SummarizedTaskView}; use crate::Opt; +#[derive(OpenApi)] +#[openapi( + paths(create_dump), + tags(( + name = "Dumps", + description = "The `dumps` route allows the creation of database dumps. +Dumps are `.dump` files that can be used to launch Meilisearch. Dumps are compatible between Meilisearch versions. +Creating a dump is also referred to as exporting it, whereas launching Meilisearch with a dump is referred to as importing it. +During a [dump export](https://www.meilisearch.com/docs/reference/api/dump#create-a-dump), all indexes of the current instance are +exported—together with their documents and settings—and saved as a single `.dump` file. During a dump import, +all indexes contained in the indicated `.dump` file are imported along with their associated documents and settings. +Any existing index with the same uid as an index in the dump file will be overwritten. +Dump imports are [performed at launch](https://www.meilisearch.com/docs/learn/advanced/dumps#importing-a-dump) using an option.", + external_docs(url = "https://www.meilisearch.com/docs/reference/api/dump"), + + )), +)] +pub struct DumpApi; + pub fn configure(cfg: &mut web::ServiceConfig) { cfg.service(web::resource("").route(web::post().to(SeqHandler(create_dump)))); } +/// Create a dump +/// +/// Triggers a dump creation process. Once the process is complete, a dump is created in the +/// [dump directory](https://www.meilisearch.com/docs/learn/self_hosted/configure_meilisearch_at_launch#dump-directory). +/// If the dump directory does not exist yet, it will be created. +#[utoipa::path( + post, + path = "/", + tag = "Dumps", + security(("Bearer" = ["dumps.create", "dumps.*", "*"])), + responses( + (status = 202, description = "Dump is being created", body = SummarizedTaskView, content_type = "application/json", example = json!( + { + "taskUid": 0, + "indexUid": null, + "status": "enqueued", + "type": "DumpCreation", + "enqueuedAt": "2021-01-01T09:39:00.000000Z" + } + )), + (status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!( + { + "message": "The Authorization header is missing. It must use the bearer authorization method.", + "code": "missing_authorization_header", + "type": "auth", + "link": "https://docs.meilisearch.com/errors#missing_authorization_header" + } + )), + ) +)] pub async fn create_dump( index_scheduler: GuardedData, Data>, auth_controller: GuardedData, Data>, diff --git a/meilisearch/src/routes/indexes/mod.rs b/meilisearch/src/routes/indexes/mod.rs index 35b747ccf..cca017d41 100644 --- a/meilisearch/src/routes/indexes/mod.rs +++ b/meilisearch/src/routes/indexes/mod.rs @@ -16,6 +16,7 @@ use serde::Serialize; use serde_json::json; use time::OffsetDateTime; use tracing::debug; +use utoipa::ToSchema; use super::{get_task_id, Pagination, SummarizedTaskView, PAGINATION_DEFAULT_LIMIT}; use crate::analytics::Analytics; @@ -247,12 +248,12 @@ pub async fn delete_index( } /// Stats of an `Index`, as known to the `stats` route. -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, ToSchema)] #[serde(rename_all = "camelCase")] pub struct IndexStats { /// Number of documents in the index pub number_of_documents: u64, - /// Whether the index is currently performing indexation, according to the scheduler. + /// Whether or not the index is currently ingesting document pub is_indexing: bool, /// Association of every field name with the number of times it occurs in the documents. pub field_distribution: FieldDistribution, diff --git a/meilisearch/src/routes/logs.rs b/meilisearch/src/routes/logs.rs index 57e2cbd22..5115c3256 100644 --- a/meilisearch/src/routes/logs.rs +++ b/meilisearch/src/routes/logs.rs @@ -14,9 +14,11 @@ use index_scheduler::IndexScheduler; use meilisearch_types::deserr::DeserrJsonError; use meilisearch_types::error::deserr_codes::*; use meilisearch_types::error::{Code, ResponseError}; +use serde::Serialize; use tokio::sync::mpsc; use tracing_subscriber::filter::Targets; use tracing_subscriber::Layer; +use utoipa::{OpenApi, ToSchema}; use crate::error::MeilisearchHttpError; use crate::extractors::authentication::policies::*; @@ -24,6 +26,20 @@ use crate::extractors::authentication::GuardedData; use crate::extractors::sequential_extractor::SeqHandler; use crate::{LogRouteHandle, LogStderrHandle}; + +#[derive(OpenApi)] +#[openapi( + paths(get_logs, cancel_logs, update_stderr_target), + tags(( + name = "Logs", + description = "Everything about retrieving or customizing logs. +Currently [experimental](https://www.meilisearch.com/docs/learn/experimental/overview).", + external_docs(url = "https://www.meilisearch.com/docs/learn/experimental/log_customization"), + + )), +)] +pub struct LogsApi; + pub fn configure(cfg: &mut web::ServiceConfig) { cfg.service( web::resource("stream") @@ -33,12 +49,16 @@ pub fn configure(cfg: &mut web::ServiceConfig) { .service(web::resource("stderr").route(web::post().to(SeqHandler(update_stderr_target)))); } -#[derive(Debug, Default, Clone, Copy, Deserr, PartialEq, Eq)] +#[derive(Debug, Default, Clone, Copy, Deserr, Serialize, PartialEq, Eq, ToSchema)] #[deserr(rename_all = camelCase)] +#[schema(rename_all = "camelCase")] pub enum LogMode { + /// Output the logs in a human readable form. #[default] Human, + /// Output the logs in json. Json, + /// Output the logs in the firefox profiler format. They can then be loaded and visualized at https://profiler.firefox.com/ Profile, } @@ -83,16 +103,26 @@ impl MergeWithError for DeserrJsonError { } } -#[derive(Debug, Deserr)] +#[derive(Debug, Deserr, ToSchema)] #[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields, validate = validate_get_logs -> DeserrJsonError)] +#[schema(rename_all = "camelCase")] pub struct GetLogs { + /// Lets you specify which parts of the code you want to inspect and is formatted like that: code_part=log_level,code_part=log_level + /// - If the `code_part` is missing, then the `log_level` will be applied to everything. + /// - If the `log_level` is missing, then the `code_part` will be selected in `info` log level. #[deserr(default = "info".parse().unwrap(), try_from(&String) = MyTargets::from_str -> DeserrJsonError)] + #[schema(value_type = String, default = "info", example = json!("milli=trace,index_scheduler,actix_web=off"))] target: MyTargets, + /// Lets you customize the format of the logs. #[deserr(default, error = DeserrJsonError)] + #[schema(default = LogMode::default)] mode: LogMode, + /// A boolean to indicate if you want to profile the memory as well. This is only useful while using the `profile` mode. + /// Be cautious, though; it slows down the engine a lot. #[deserr(default = false, error = DeserrJsonError)] + #[schema(default = false)] profile_memory: bool, } @@ -248,6 +278,45 @@ fn entry_stream( ) } +/// Retrieve logs +/// +/// Stream logs over HTTP. The format of the logs depends on the configuration specified in the payload. +/// The logs are sent as multi-part, and the stream never stops, so make sure your clients correctly handle that. +/// To make the server stop sending you logs, you can call the `DELETE /logs/stream` route. +/// +/// There can only be one listener at a timeand an error will be returned if you call this route while it's being used by another client. +#[utoipa::path( + post, + path = "/stream", + tag = "Logs", + security(("Bearer" = ["metrics.get", "metrics.*", "*"])), + request_body = GetLogs, + responses( + (status = OK, description = "Logs are being returned", body = String, content_type = "application/json", example = json!( + r#" +2024-10-08T13:35:02.643750Z WARN HTTP request{method=GET host="localhost:7700" route=/metrics query_parameters= user_agent=HTTPie/3.2.3 status_code=400 error=Getting metrics requires enabling the `metrics` experimental feature. See https://github.com/meilisearch/product/discussions/625}: tracing_actix_web::middleware: Error encountered while processing the incoming HTTP request: ResponseError { code: 400, message: "Getting metrics requires enabling the `metrics` experimental feature. See https://github.com/meilisearch/product/discussions/625", error_code: "feature_not_enabled", error_type: "invalid_request", error_link: "https://docs.meilisearch.com/errors#feature_not_enabled" } +2024-10-08T13:35:02.644191Z INFO HTTP request{method=GET host="localhost:7700" route=/metrics query_parameters= user_agent=HTTPie/3.2.3 status_code=400 error=Getting metrics requires enabling the `metrics` experimental feature. See https://github.com/meilisearch/product/discussions/625}: meilisearch: close time.busy=1.66ms time.idle=658µs +2024-10-08T13:35:18.564152Z INFO HTTP request{method=PATCH host="localhost:7700" route=/experimental-features query_parameters= user_agent=curl/8.6.0 status_code=200}: meilisearch: close time.busy=1.17ms time.idle=127µs +2024-10-08T13:35:23.094987Z INFO HTTP request{method=GET host="localhost:7700" route=/metrics query_parameters= user_agent=HTTPie/3.2.3 status_code=200}: meilisearch: close time.busy=2.12ms time.idle=595µs "# + )), + (status = 400, description = "The route is already being used", body = ResponseError, content_type = "application/json", example = json!( + { + "message": "The `/logs/stream` route is currently in use by someone else.", + "code": "bad_request", + "type": "invalid_request", + "link": "https://docs.meilisearch.com/errors#bad_request" + } + )), + (status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!( + { + "message": "The Authorization header is missing. It must use the bearer authorization method.", + "code": "missing_authorization_header", + "type": "auth", + "link": "https://docs.meilisearch.com/errors#missing_authorization_header" + } + )), + ) +)] pub async fn get_logs( index_scheduler: GuardedData, Data>, logs: Data, @@ -280,6 +349,27 @@ pub async fn get_logs( } } + +/// Stop retrieving logs +/// +/// Call this route to make the engine stops sending logs through the `POST /logs/stream` route. +#[utoipa::path( + delete, + path = "/stream", + tag = "Logs", + security(("Bearer" = ["metrics.get", "metrics.*", "*"])), + responses( + (status = NO_CONTENT, description = "Logs are being returned"), + (status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!( + { + "message": "The Authorization header is missing. It must use the bearer authorization method.", + "code": "missing_authorization_header", + "type": "auth", + "link": "https://docs.meilisearch.com/errors#missing_authorization_header" + } + )), + ) +)] pub async fn cancel_logs( index_scheduler: GuardedData, Data>, logs: Data, @@ -293,13 +383,38 @@ pub async fn cancel_logs( Ok(HttpResponse::NoContent().finish()) } -#[derive(Debug, Deserr)] +#[derive(Debug, Deserr, ToSchema)] #[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)] pub struct UpdateStderrLogs { + /// Lets you specify which parts of the code you want to inspect and is formatted like that: code_part=log_level,code_part=log_level + /// - If the `code_part` is missing, then the `log_level` will be applied to everything. + /// - If the `log_level` is missing, then the `code_part` will be selected in `info` log level. #[deserr(default = "info".parse().unwrap(), try_from(&String) = MyTargets::from_str -> DeserrJsonError)] + #[schema(value_type = String, default = "info", example = json!("milli=trace,index_scheduler,actix_web=off"))] target: MyTargets, } +/// Update target of the console logs +/// +/// This route lets you specify at runtime the level of the console logs outputted on stderr. +#[utoipa::path( + post, + path = "/stderr", + tag = "Logs", + request_body = UpdateStderrLogs, + security(("Bearer" = ["metrics.get", "metrics.*", "*"])), + responses( + (status = NO_CONTENT, description = "The console logs have been updated"), + (status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!( + { + "message": "The Authorization header is missing. It must use the bearer authorization method.", + "code": "missing_authorization_header", + "type": "auth", + "link": "https://docs.meilisearch.com/errors#missing_authorization_header" + } + )), + ) +)] pub async fn update_stderr_target( index_scheduler: GuardedData, Data>, logs: Data, diff --git a/meilisearch/src/routes/metrics.rs b/meilisearch/src/routes/metrics.rs index 7a13a758f..7040a1db7 100644 --- a/meilisearch/src/routes/metrics.rs +++ b/meilisearch/src/routes/metrics.rs @@ -6,15 +6,106 @@ use meilisearch_auth::AuthController; use meilisearch_types::error::ResponseError; use meilisearch_types::keys::actions; use prometheus::{Encoder, TextEncoder}; +use utoipa::OpenApi; use crate::extractors::authentication::policies::ActionPolicy; use crate::extractors::authentication::{AuthenticationError, GuardedData}; use crate::routes::create_all_stats; +#[derive(OpenApi)] +#[openapi(paths(get_metrics))] +pub struct MetricApi; + pub fn configure(config: &mut web::ServiceConfig) { config.service(web::resource("").route(web::get().to(get_metrics))); } +/// Get prometheus metrics +/// +/// Retrieve metrics on the engine. See https://www.meilisearch.com/docs/learn/experimental/metrics +/// Currently, [the feature is experimental](https://www.meilisearch.com/docs/learn/experimental/overview) +/// which means it must be enabled. +#[utoipa::path( + get, + path = "/", + tag = "Stats", + security(("Bearer" = ["metrics.get", "metrics.*", "*"])), + responses( + (status = 200, description = "The metrics of the instance", body = String, content_type = "text/plain", example = json!( + r#" +# HELP meilisearch_db_size_bytes Meilisearch DB Size In Bytes +# TYPE meilisearch_db_size_bytes gauge +meilisearch_db_size_bytes 1130496 +# HELP meilisearch_http_requests_total Meilisearch HTTP requests total +# TYPE meilisearch_http_requests_total counter +meilisearch_http_requests_total{method="GET",path="/metrics",status="400"} 1 +meilisearch_http_requests_total{method="PATCH",path="/experimental-features",status="200"} 1 +# HELP meilisearch_http_response_time_seconds Meilisearch HTTP response times +# TYPE meilisearch_http_response_time_seconds histogram +meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.005"} 0 +meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.01"} 0 +meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.025"} 0 +meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.05"} 0 +meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.075"} 0 +meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.1"} 0 +meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.25"} 0 +meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.5"} 0 +meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="0.75"} 0 +meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="1"} 0 +meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="2.5"} 0 +meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="5"} 0 +meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="7.5"} 0 +meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="10"} 0 +meilisearch_http_response_time_seconds_bucket{method="GET",path="/metrics",le="+Inf"} 0 +meilisearch_http_response_time_seconds_sum{method="GET",path="/metrics"} 0 +meilisearch_http_response_time_seconds_count{method="GET",path="/metrics"} 0 +# HELP meilisearch_index_count Meilisearch Index Count +# TYPE meilisearch_index_count gauge +meilisearch_index_count 1 +# HELP meilisearch_index_docs_count Meilisearch Index Docs Count +# TYPE meilisearch_index_docs_count gauge +meilisearch_index_docs_count{index="mieli"} 2 +# HELP meilisearch_is_indexing Meilisearch Is Indexing +# TYPE meilisearch_is_indexing gauge +meilisearch_is_indexing 0 +# HELP meilisearch_last_update Meilisearch Last Update +# TYPE meilisearch_last_update gauge +meilisearch_last_update 1726675964 +# HELP meilisearch_nb_tasks Meilisearch Number of tasks +# TYPE meilisearch_nb_tasks gauge +meilisearch_nb_tasks{kind="indexes",value="mieli"} 39 +meilisearch_nb_tasks{kind="statuses",value="canceled"} 0 +meilisearch_nb_tasks{kind="statuses",value="enqueued"} 0 +meilisearch_nb_tasks{kind="statuses",value="failed"} 4 +meilisearch_nb_tasks{kind="statuses",value="processing"} 0 +meilisearch_nb_tasks{kind="statuses",value="succeeded"} 35 +meilisearch_nb_tasks{kind="types",value="documentAdditionOrUpdate"} 9 +meilisearch_nb_tasks{kind="types",value="documentDeletion"} 0 +meilisearch_nb_tasks{kind="types",value="documentEdition"} 0 +meilisearch_nb_tasks{kind="types",value="dumpCreation"} 0 +meilisearch_nb_tasks{kind="types",value="indexCreation"} 0 +meilisearch_nb_tasks{kind="types",value="indexDeletion"} 8 +meilisearch_nb_tasks{kind="types",value="indexSwap"} 0 +meilisearch_nb_tasks{kind="types",value="indexUpdate"} 0 +meilisearch_nb_tasks{kind="types",value="settingsUpdate"} 22 +meilisearch_nb_tasks{kind="types",value="snapshotCreation"} 0 +meilisearch_nb_tasks{kind="types",value="taskCancelation"} 0 +meilisearch_nb_tasks{kind="types",value="taskDeletion"} 0 +# HELP meilisearch_used_db_size_bytes Meilisearch Used DB Size In Bytes +# TYPE meilisearch_used_db_size_bytes gauge +meilisearch_used_db_size_bytes 409600 +"# + )), + (status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!( + { + "message": "The Authorization header is missing. It must use the bearer authorization method.", + "code": "missing_authorization_header", + "type": "auth", + "link": "https://docs.meilisearch.com/errors#missing_authorization_header" + } + )), + ) +)] pub async fn get_metrics( index_scheduler: GuardedData, Data>, auth_controller: Data, diff --git a/meilisearch/src/routes/mod.rs b/meilisearch/src/routes/mod.rs index b4725adad..b19631495 100644 --- a/meilisearch/src/routes/mod.rs +++ b/meilisearch/src/routes/mod.rs @@ -27,6 +27,10 @@ use utoipa::ToSchema; use utoipa_rapidoc::RapiDoc; use utoipa_scalar::{Scalar, Servable as ScalarServable}; +use self::indexes::IndexStats; +use self::logs::GetLogs; +use self::logs::LogMode; +use self::logs::UpdateStderrLogs; use self::open_api_utils::OpenApiAuth; use self::tasks::AllTasks; @@ -46,10 +50,16 @@ pub mod tasks; #[derive(OpenApi)] #[openapi( - nest((path = "/tasks", api = tasks::TaskApi) ), - paths(get_health, get_version), + nest( + (path = "/tasks", api = tasks::TaskApi), + (path = "/snapshots", api = snapshot::SnapshotApi), + (path = "/dumps", api = dump::DumpApi), + (path = "/metrics", api = metrics::MetricApi), + (path = "/logs", api = logs::LogsApi), + ), + paths(get_health, get_version, get_stats), modifiers(&OpenApiAuth), - components(schemas(HealthStatus, HealthResponse, VersionResponse, Code, ErrorType, AllTasks, TaskView, Status, DetailsView, ResponseError, Settings, Settings, TypoSettings, MinWordSizeTyposSetting, FacetingSettings, PaginationSettings, SummarizedTaskView, Kind)) + components(schemas(UpdateStderrLogs, LogMode, GetLogs, IndexStats, Stats, HealthStatus, HealthResponse, VersionResponse, Code, ErrorType, AllTasks, TaskView, Status, DetailsView, ResponseError, Settings, Settings, TypoSettings, MinWordSizeTyposSetting, FacetingSettings, PaginationSettings, SummarizedTaskView, Kind)) )] pub struct MeilisearchApi; @@ -313,17 +323,56 @@ pub async fn running() -> HttpResponse { HttpResponse::Ok().json(serde_json::json!({ "status": "Meilisearch is running" })) } -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, ToSchema)] #[serde(rename_all = "camelCase")] pub struct Stats { + /// The size of the database, in bytes. pub database_size: u64, #[serde(skip)] pub used_database_size: u64, + /// The date of the last update in the RFC 3339 formats. Can be `null` if no update has ever been processed. #[serde(serialize_with = "time::serde::rfc3339::option::serialize")] pub last_update: Option, + /// The stats of every individual index your API key lets you access. + #[schema(value_type = HashMap)] pub indexes: BTreeMap, } +/// Get stats of all indexes. +/// +/// Get stats of all indexes. +#[utoipa::path( + get, + path = "/stats", + tag = "Stats", + security(("Bearer" = ["stats.get", "stats.*", "*"])), + responses( + (status = 200, description = "The stats of the instance", body = Stats, content_type = "application/json", example = json!( + { + "databaseSize": 567, + "lastUpdate": "2019-11-20T09:40:33.711324Z", + "indexes": { + "movies": { + "numberOfDocuments": 10, + "isIndexing": true, + "fieldDistribution": { + "genre": 10, + "author": 9 + } + } + } + } + )), + (status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!( + { + "message": "The Authorization header is missing. It must use the bearer authorization method.", + "code": "missing_authorization_header", + "type": "auth", + "link": "https://docs.meilisearch.com/errors#missing_authorization_header" + } + )), + ) +)] async fn get_stats( index_scheduler: GuardedData, Data>, auth_controller: GuardedData, Data>, diff --git a/meilisearch/src/routes/snapshot.rs b/meilisearch/src/routes/snapshot.rs index 84673729f..91f2c6cfd 100644 --- a/meilisearch/src/routes/snapshot.rs +++ b/meilisearch/src/routes/snapshot.rs @@ -5,6 +5,7 @@ use meilisearch_types::error::ResponseError; use meilisearch_types::tasks::KindWithContent; use serde_json::json; use tracing::debug; +use utoipa::OpenApi; use crate::analytics::Analytics; use crate::extractors::authentication::policies::*; @@ -13,10 +14,54 @@ use crate::extractors::sequential_extractor::SeqHandler; use crate::routes::{get_task_id, is_dry_run, SummarizedTaskView}; use crate::Opt; +#[derive(OpenApi)] +#[openapi( + paths(create_snapshot), + tags(( + name = "Snapshots", + description = "The snapshots route allows the creation of database snapshots. Snapshots are .snapshot files that can be used to launch Meilisearch. +Creating a snapshot is also referred to as exporting it, whereas launching Meilisearch with a snapshot is referred to as importing it. +During a snapshot export, all indexes of the current instance are exported—together with their documents and settings—and saved as a single .snapshot file. +During a snapshot import, all indexes contained in the indicated .snapshot file are imported along with their associated documents and settings. +Snapshot imports are performed at launch using an option.", + external_docs(url = "https://www.meilisearch.com/docs/reference/api/snapshots"), + + )), +)] +pub struct SnapshotApi; + pub fn configure(cfg: &mut web::ServiceConfig) { cfg.service(web::resource("").route(web::post().to(SeqHandler(create_snapshot)))); } +/// Create a snapshot +/// +/// Triggers a snapshot creation process. Once the process is complete, a snapshot is created in the snapshot directory. If the snapshot directory does not exist yet, it will be created. +#[utoipa::path( + post, + path = "/", + tag = "Snapshots", + security(("Bearer" = ["snapshots.create", "snapshots.*", "*"])), + responses( + (status = 202, description = "Snapshot is being created", body = SummarizedTaskView, content_type = "application/json", example = json!( + { + "taskUid": 0, + "indexUid": null, + "status": "enqueued", + "type": "snapshotCreation", + "enqueuedAt": "2021-01-01T09:39:00.000000Z" + } + )), + (status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json", example = json!( + { + "message": "The Authorization header is missing. It must use the bearer authorization method.", + "code": "missing_authorization_header", + "type": "auth", + "link": "https://docs.meilisearch.com/errors#missing_authorization_header" + } + )), + ) +)] pub async fn create_snapshot( index_scheduler: GuardedData, Data>, req: HttpRequest, diff --git a/meilisearch/src/routes/tasks.rs b/meilisearch/src/routes/tasks.rs index bb22d7810..2b3a49bf1 100644 --- a/meilisearch/src/routes/tasks.rs +++ b/meilisearch/src/routes/tasks.rs @@ -29,29 +29,6 @@ use crate::Opt; const DEFAULT_LIMIT: u32 = 20; - -#[derive(Debug, Serialize)] -pub struct OpenApiAuth; - -impl utoipa::Modify for OpenApiAuth { - fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) { - if let Some(schema) = openapi.components.as_mut() { - schema.add_security_scheme( - "Bearer", - utoipa::openapi::security::SecurityScheme::Http( - utoipa::openapi::security::HttpBuilder::new() - .scheme(utoipa::openapi::security::HttpAuthScheme::Bearer) - .bearer_format("Uuidv4, string or JWT") - .description(Some( -"An API key is a token that you provide when making API calls. Include the token in a header parameter called `Authorization`. -Example: `Authorization: Bearer 8fece4405662dd830e4cb265e7e047aab2e79672a760a12712d2a263c9003509`")) - .build(), - ), - ); - } - } -} - #[derive(OpenApi)] #[openapi( paths(get_tasks, delete_tasks, cancel_tasks, get_task),