Introduce a new route to export documents and enqueue the export task

This commit is contained in:
Clément Renault 2025-06-12 16:23:48 +02:00 committed by Kerollmops
parent ae8c1461e1
commit e74c3b692a
No known key found for this signature in database
GPG key ID: F250A4C4E3AE5F5F
14 changed files with 303 additions and 10 deletions

View file

@ -0,0 +1,105 @@
use actix_web::web::{self, Data};
use actix_web::{HttpRequest, HttpResponse};
use deserr::actix_web::AwebJson;
use deserr::Deserr;
use index_scheduler::IndexScheduler;
use meilisearch_types::deserr::DeserrJsonError;
use meilisearch_types::error::deserr_codes::*;
use meilisearch_types::error::ResponseError;
use meilisearch_types::index_uid_pattern::IndexUidPattern;
use meilisearch_types::keys::actions;
use meilisearch_types::tasks::KindWithContent;
use serde::Serialize;
use tracing::debug;
use utoipa::{OpenApi, ToSchema};
use crate::analytics::Analytics;
use crate::extractors::authentication::policies::ActionPolicy;
use crate::extractors::authentication::GuardedData;
use crate::routes::{get_task_id, is_dry_run, SummarizedTaskView};
use crate::Opt;
#[derive(OpenApi)]
#[openapi(
paths(export),
tags((
name = "Export",
description = "The `/export` route allows you to trigger an export process to a remote Meilisearch instance.",
external_docs(url = "https://www.meilisearch.com/docs/reference/api/export"),
)),
)]
pub struct ExportApi;
pub fn configure(cfg: &mut web::ServiceConfig) {
cfg.service(web::resource("").route(web::post().to(export)));
}
#[utoipa::path(
get,
path = "",
tag = "Export",
security(("Bearer" = ["export", "*"])),
responses(
(status = OK, description = "Known nodes are returned", body = Export, content_type = "application/json", example = json!(
{
"indexes": ["movie", "steam-*"],
"skip_embeddings": true,
"apiKey": "meilisearch-api-key"
})),
(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 export(
index_scheduler: GuardedData<ActionPolicy<{ actions::EXPORT }>, Data<IndexScheduler>>,
export: AwebJson<Export, DeserrJsonError>,
req: HttpRequest,
opt: web::Data<Opt>,
_analytics: Data<Analytics>,
) -> Result<HttpResponse, ResponseError> {
// TODO make it experimental?
// index_scheduler.features().check_network("Using the /network route")?;
let export = export.into_inner();
debug!(returns = ?export, "Trigger export");
let Export { url, api_key, indexes, skip_embeddings } = export;
let task = KindWithContent::Export { url, api_key, indexes, skip_embeddings };
let uid = get_task_id(&req, &opt)?;
let dry_run = is_dry_run(&req, &opt)?;
let task: SummarizedTaskView =
tokio::task::spawn_blocking(move || index_scheduler.register(task, uid, dry_run))
.await??
.into();
Ok(HttpResponse::Ok().json(task))
}
#[derive(Debug, Deserr, ToSchema, Serialize)]
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
#[schema(rename_all = "camelCase")]
pub struct Export {
#[schema(value_type = Option<String>, example = json!("https://ms-1234.heaven.meilisearch.com"))]
#[serde(default)]
#[deserr(default, error = DeserrJsonError<InvalidExportUrl>)]
pub url: String,
#[schema(value_type = Option<String>, example = json!("1234abcd"))]
#[serde(default)]
#[deserr(default, error = DeserrJsonError<InvalidExportApiKey>)]
pub api_key: Option<String>,
#[schema(value_type = Option<BTreeSet<String>>, example = json!(["movies", "steam-*"]))]
#[deserr(default, error = DeserrJsonError<InvalidExportIndexesPatterns>)]
#[serde(default)]
pub indexes: Vec<IndexUidPattern>,
#[schema(value_type = Option<bool>, example = json!("true"))]
#[serde(default)]
#[deserr(default, error = DeserrJsonError<InvalidExportSkipEmbeddings>)]
pub skip_embeddings: bool,
}

View file

@ -54,6 +54,7 @@ mod api_key;
pub mod batches;
pub mod chats;
mod dump;
mod export;
pub mod features;
pub mod indexes;
mod logs;
@ -84,6 +85,7 @@ mod tasks_test;
(path = "/multi-search", api = multi_search::MultiSearchApi),
(path = "/swap-indexes", api = swap_indexes::SwapIndexesApi),
(path = "/experimental-features", api = features::ExperimentalFeaturesApi),
(path = "/export", api = export::ExportApi),
(path = "/network", api = network::NetworkApi),
),
paths(get_health, get_version, get_stats),
@ -115,6 +117,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
.service(web::scope("/metrics").configure(metrics::configure))
.service(web::scope("/experimental-features").configure(features::configure))
.service(web::scope("/network").configure(network::configure))
.service(web::scope("/export").configure(export::configure))
.service(web::scope("/chats").configure(chats::configure));
#[cfg(feature = "swagger")]