implements more routes

This commit is contained in:
Tamo 2024-10-08 17:05:06 +02:00
parent 6fd5258ec1
commit 143a866b48
7 changed files with 360 additions and 32 deletions

View File

@ -6,6 +6,7 @@ use meilisearch_types::error::ResponseError;
use meilisearch_types::tasks::KindWithContent; use meilisearch_types::tasks::KindWithContent;
use serde_json::json; use serde_json::json;
use tracing::debug; use tracing::debug;
use utoipa::OpenApi;
use crate::analytics::Analytics; use crate::analytics::Analytics;
use crate::extractors::authentication::policies::*; 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::routes::{get_task_id, is_dry_run, SummarizedTaskView};
use crate::Opt; 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
exportedtogether with their documents and settingsand 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) { pub fn configure(cfg: &mut web::ServiceConfig) {
cfg.service(web::resource("").route(web::post().to(SeqHandler(create_dump)))); 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( pub async fn create_dump(
index_scheduler: GuardedData<ActionPolicy<{ actions::DUMPS_CREATE }>, Data<IndexScheduler>>, index_scheduler: GuardedData<ActionPolicy<{ actions::DUMPS_CREATE }>, Data<IndexScheduler>>,
auth_controller: GuardedData<ActionPolicy<{ actions::DUMPS_CREATE }>, Data<AuthController>>, auth_controller: GuardedData<ActionPolicy<{ actions::DUMPS_CREATE }>, Data<AuthController>>,

View File

@ -16,6 +16,7 @@ use serde::Serialize;
use serde_json::json; use serde_json::json;
use time::OffsetDateTime; use time::OffsetDateTime;
use tracing::debug; use tracing::debug;
use utoipa::ToSchema;
use super::{get_task_id, Pagination, SummarizedTaskView, PAGINATION_DEFAULT_LIMIT}; use super::{get_task_id, Pagination, SummarizedTaskView, PAGINATION_DEFAULT_LIMIT};
use crate::analytics::Analytics; use crate::analytics::Analytics;
@ -247,12 +248,12 @@ pub async fn delete_index(
} }
/// Stats of an `Index`, as known to the `stats` route. /// Stats of an `Index`, as known to the `stats` route.
#[derive(Serialize, Debug)] #[derive(Serialize, Debug, ToSchema)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct IndexStats { pub struct IndexStats {
/// Number of documents in the index /// Number of documents in the index
pub number_of_documents: u64, 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, pub is_indexing: bool,
/// Association of every field name with the number of times it occurs in the documents. /// Association of every field name with the number of times it occurs in the documents.
pub field_distribution: FieldDistribution, pub field_distribution: FieldDistribution,

View File

@ -14,9 +14,11 @@ use index_scheduler::IndexScheduler;
use meilisearch_types::deserr::DeserrJsonError; use meilisearch_types::deserr::DeserrJsonError;
use meilisearch_types::error::deserr_codes::*; use meilisearch_types::error::deserr_codes::*;
use meilisearch_types::error::{Code, ResponseError}; use meilisearch_types::error::{Code, ResponseError};
use serde::Serialize;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tracing_subscriber::filter::Targets; use tracing_subscriber::filter::Targets;
use tracing_subscriber::Layer; use tracing_subscriber::Layer;
use utoipa::{OpenApi, ToSchema};
use crate::error::MeilisearchHttpError; use crate::error::MeilisearchHttpError;
use crate::extractors::authentication::policies::*; use crate::extractors::authentication::policies::*;
@ -24,6 +26,20 @@ use crate::extractors::authentication::GuardedData;
use crate::extractors::sequential_extractor::SeqHandler; use crate::extractors::sequential_extractor::SeqHandler;
use crate::{LogRouteHandle, LogStderrHandle}; 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) { pub fn configure(cfg: &mut web::ServiceConfig) {
cfg.service( cfg.service(
web::resource("stream") 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)))); .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)] #[deserr(rename_all = camelCase)]
#[schema(rename_all = "camelCase")]
pub enum LogMode { pub enum LogMode {
/// Output the logs in a human readable form.
#[default] #[default]
Human, Human,
/// Output the logs in json.
Json, Json,
/// Output the logs in the firefox profiler format. They can then be loaded and visualized at https://profiler.firefox.com/
Profile, Profile,
} }
@ -83,16 +103,26 @@ impl MergeWithError<MyParseError> for DeserrJsonError<BadRequest> {
} }
} }
#[derive(Debug, Deserr)] #[derive(Debug, Deserr, ToSchema)]
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields, validate = validate_get_logs -> DeserrJsonError<InvalidSettingsTypoTolerance>)] #[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields, validate = validate_get_logs -> DeserrJsonError<InvalidSettingsTypoTolerance>)]
#[schema(rename_all = "camelCase")]
pub struct GetLogs { 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<BadRequest>)] #[deserr(default = "info".parse().unwrap(), try_from(&String) = MyTargets::from_str -> DeserrJsonError<BadRequest>)]
#[schema(value_type = String, default = "info", example = json!("milli=trace,index_scheduler,actix_web=off"))]
target: MyTargets, target: MyTargets,
/// Lets you customize the format of the logs.
#[deserr(default, error = DeserrJsonError<BadRequest>)] #[deserr(default, error = DeserrJsonError<BadRequest>)]
#[schema(default = LogMode::default)]
mode: LogMode, 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<BadRequest>)] #[deserr(default = false, error = DeserrJsonError<BadRequest>)]
#[schema(default = false)]
profile_memory: bool, 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( pub async fn get_logs(
index_scheduler: GuardedData<ActionPolicy<{ actions::METRICS_GET }>, Data<IndexScheduler>>, index_scheduler: GuardedData<ActionPolicy<{ actions::METRICS_GET }>, Data<IndexScheduler>>,
logs: Data<LogRouteHandle>, logs: Data<LogRouteHandle>,
@ -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( pub async fn cancel_logs(
index_scheduler: GuardedData<ActionPolicy<{ actions::METRICS_GET }>, Data<IndexScheduler>>, index_scheduler: GuardedData<ActionPolicy<{ actions::METRICS_GET }>, Data<IndexScheduler>>,
logs: Data<LogRouteHandle>, logs: Data<LogRouteHandle>,
@ -293,13 +383,38 @@ pub async fn cancel_logs(
Ok(HttpResponse::NoContent().finish()) Ok(HttpResponse::NoContent().finish())
} }
#[derive(Debug, Deserr)] #[derive(Debug, Deserr, ToSchema)]
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)] #[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
pub struct UpdateStderrLogs { 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<BadRequest>)] #[deserr(default = "info".parse().unwrap(), try_from(&String) = MyTargets::from_str -> DeserrJsonError<BadRequest>)]
#[schema(value_type = String, default = "info", example = json!("milli=trace,index_scheduler,actix_web=off"))]
target: MyTargets, 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( pub async fn update_stderr_target(
index_scheduler: GuardedData<ActionPolicy<{ actions::METRICS_GET }>, Data<IndexScheduler>>, index_scheduler: GuardedData<ActionPolicy<{ actions::METRICS_GET }>, Data<IndexScheduler>>,
logs: Data<LogStderrHandle>, logs: Data<LogStderrHandle>,

View File

@ -6,15 +6,106 @@ use meilisearch_auth::AuthController;
use meilisearch_types::error::ResponseError; use meilisearch_types::error::ResponseError;
use meilisearch_types::keys::actions; use meilisearch_types::keys::actions;
use prometheus::{Encoder, TextEncoder}; use prometheus::{Encoder, TextEncoder};
use utoipa::OpenApi;
use crate::extractors::authentication::policies::ActionPolicy; use crate::extractors::authentication::policies::ActionPolicy;
use crate::extractors::authentication::{AuthenticationError, GuardedData}; use crate::extractors::authentication::{AuthenticationError, GuardedData};
use crate::routes::create_all_stats; use crate::routes::create_all_stats;
#[derive(OpenApi)]
#[openapi(paths(get_metrics))]
pub struct MetricApi;
pub fn configure(config: &mut web::ServiceConfig) { pub fn configure(config: &mut web::ServiceConfig) {
config.service(web::resource("").route(web::get().to(get_metrics))); 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( pub async fn get_metrics(
index_scheduler: GuardedData<ActionPolicy<{ actions::METRICS_GET }>, Data<IndexScheduler>>, index_scheduler: GuardedData<ActionPolicy<{ actions::METRICS_GET }>, Data<IndexScheduler>>,
auth_controller: Data<AuthController>, auth_controller: Data<AuthController>,

View File

@ -27,6 +27,10 @@ use utoipa::ToSchema;
use utoipa_rapidoc::RapiDoc; use utoipa_rapidoc::RapiDoc;
use utoipa_scalar::{Scalar, Servable as ScalarServable}; 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::open_api_utils::OpenApiAuth;
use self::tasks::AllTasks; use self::tasks::AllTasks;
@ -46,10 +50,16 @@ pub mod tasks;
#[derive(OpenApi)] #[derive(OpenApi)]
#[openapi( #[openapi(
nest((path = "/tasks", api = tasks::TaskApi) ), nest(
paths(get_health, get_version), (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), modifiers(&OpenApiAuth),
components(schemas(HealthStatus, HealthResponse, VersionResponse, Code, ErrorType, AllTasks, TaskView, Status, DetailsView, ResponseError, Settings<Unchecked>, Settings<Checked>, TypoSettings, MinWordSizeTyposSetting, FacetingSettings, PaginationSettings, SummarizedTaskView, Kind)) components(schemas(UpdateStderrLogs, LogMode, GetLogs, IndexStats, Stats, HealthStatus, HealthResponse, VersionResponse, Code, ErrorType, AllTasks, TaskView, Status, DetailsView, ResponseError, Settings<Unchecked>, Settings<Checked>, TypoSettings, MinWordSizeTyposSetting, FacetingSettings, PaginationSettings, SummarizedTaskView, Kind))
)] )]
pub struct MeilisearchApi; pub struct MeilisearchApi;
@ -313,17 +323,56 @@ pub async fn running() -> HttpResponse {
HttpResponse::Ok().json(serde_json::json!({ "status": "Meilisearch is running" })) HttpResponse::Ok().json(serde_json::json!({ "status": "Meilisearch is running" }))
} }
#[derive(Serialize, Debug)] #[derive(Serialize, Debug, ToSchema)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Stats { pub struct Stats {
/// The size of the database, in bytes.
pub database_size: u64, pub database_size: u64,
#[serde(skip)] #[serde(skip)]
pub used_database_size: u64, 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")] #[serde(serialize_with = "time::serde::rfc3339::option::serialize")]
pub last_update: Option<OffsetDateTime>, pub last_update: Option<OffsetDateTime>,
/// The stats of every individual index your API key lets you access.
#[schema(value_type = HashMap<String, indexes::IndexStats>)]
pub indexes: BTreeMap<String, indexes::IndexStats>, pub indexes: BTreeMap<String, indexes::IndexStats>,
} }
/// 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( async fn get_stats(
index_scheduler: GuardedData<ActionPolicy<{ actions::STATS_GET }>, Data<IndexScheduler>>, index_scheduler: GuardedData<ActionPolicy<{ actions::STATS_GET }>, Data<IndexScheduler>>,
auth_controller: GuardedData<ActionPolicy<{ actions::STATS_GET }>, Data<AuthController>>, auth_controller: GuardedData<ActionPolicy<{ actions::STATS_GET }>, Data<AuthController>>,

View File

@ -5,6 +5,7 @@ use meilisearch_types::error::ResponseError;
use meilisearch_types::tasks::KindWithContent; use meilisearch_types::tasks::KindWithContent;
use serde_json::json; use serde_json::json;
use tracing::debug; use tracing::debug;
use utoipa::OpenApi;
use crate::analytics::Analytics; use crate::analytics::Analytics;
use crate::extractors::authentication::policies::*; 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::routes::{get_task_id, is_dry_run, SummarizedTaskView};
use crate::Opt; 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 exportedtogether with their documents and settingsand 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) { pub fn configure(cfg: &mut web::ServiceConfig) {
cfg.service(web::resource("").route(web::post().to(SeqHandler(create_snapshot)))); 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( pub async fn create_snapshot(
index_scheduler: GuardedData<ActionPolicy<{ actions::SNAPSHOTS_CREATE }>, Data<IndexScheduler>>, index_scheduler: GuardedData<ActionPolicy<{ actions::SNAPSHOTS_CREATE }>, Data<IndexScheduler>>,
req: HttpRequest, req: HttpRequest,

View File

@ -29,29 +29,6 @@ use crate::Opt;
const DEFAULT_LIMIT: u32 = 20; 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)] #[derive(OpenApi)]
#[openapi( #[openapi(
paths(get_tasks, delete_tasks, cancel_tasks, get_task), paths(get_tasks, delete_tasks, cancel_tasks, get_task),