mirror of
https://github.com/meilisearch/meilisearch.git
synced 2025-09-04 19:56:30 +00:00
Replace name by uuid
This commit is contained in:
@ -1,6 +1,7 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@ -14,5 +15,5 @@ pub struct Webhook {
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Webhooks {
|
pub struct Webhooks {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub webhooks: BTreeMap<String, Webhook>,
|
pub webhooks: BTreeMap<Uuid, Webhook>,
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,7 @@ use option::ScheduleSnapshot;
|
|||||||
use search_queue::SearchQueue;
|
use search_queue::SearchQueue;
|
||||||
use tracing::{error, info_span};
|
use tracing::{error, info_span};
|
||||||
use tracing_subscriber::filter::Targets;
|
use tracing_subscriber::filter::Targets;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::error::MeilisearchHttpError;
|
use crate::error::MeilisearchHttpError;
|
||||||
|
|
||||||
@ -339,13 +340,13 @@ pub fn setup_meilisearch(opt: &Opt) -> anyhow::Result<(Arc<IndexScheduler>, Arc<
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
let mut webhooks = index_scheduler.webhooks();
|
let mut webhooks = index_scheduler.webhooks();
|
||||||
if webhooks.webhooks.get("_cli") != cli_webhook.as_ref() {
|
if webhooks.webhooks.get(&Uuid::nil()) != cli_webhook.as_ref() {
|
||||||
match cli_webhook {
|
match cli_webhook {
|
||||||
Some(webhook) => {
|
Some(webhook) => {
|
||||||
webhooks.webhooks.insert("_cli".to_string(), webhook);
|
webhooks.webhooks.insert(Uuid::nil(), webhook);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
webhooks.webhooks.remove("_cli");
|
webhooks.webhooks.remove(&Uuid::nil());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
index_scheduler.put_webhooks(webhooks)?;
|
index_scheduler.put_webhooks(webhooks)?;
|
||||||
|
@ -16,6 +16,7 @@ use meilisearch_types::webhooks::{Webhook, Webhooks};
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
use utoipa::{OpenApi, ToSchema};
|
use utoipa::{OpenApi, ToSchema};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::analytics::{Aggregate, Analytics};
|
use crate::analytics::{Aggregate, Analytics};
|
||||||
use crate::extractors::authentication::policies::ActionPolicy;
|
use crate::extractors::authentication::policies::ActionPolicy;
|
||||||
@ -37,49 +38,9 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
|||||||
cfg.service(
|
cfg.service(
|
||||||
web::resource("")
|
web::resource("")
|
||||||
.route(web::get().to(get_webhooks))
|
.route(web::get().to(get_webhooks))
|
||||||
.route(web::patch().to(SeqHandler(patch_webhooks)))
|
.route(web::patch().to(SeqHandler(patch_webhooks))),
|
||||||
)
|
)
|
||||||
.service(
|
.service(web::resource("/{uuid}").route(web::get().to(get_webhook)));
|
||||||
web::resource("/{name}")
|
|
||||||
.route(web::get().to(get_webhook))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[utoipa::path(
|
|
||||||
get,
|
|
||||||
path = "",
|
|
||||||
tag = "Webhooks",
|
|
||||||
security(("Bearer" = ["webhooks.get", "*.get", "*"])),
|
|
||||||
responses(
|
|
||||||
(status = OK, description = "Webhooks are returned", body = WebhooksSettings, content_type = "application/json", example = json!({
|
|
||||||
"webhooks": {
|
|
||||||
"name": {
|
|
||||||
"url": "http://example.com/webhook",
|
|
||||||
},
|
|
||||||
"anotherName": {
|
|
||||||
"url": "https://your.site/on-tasks-completed",
|
|
||||||
"headers": {
|
|
||||||
"Authorization": "Bearer a-secret-token"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})),
|
|
||||||
(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_webhooks(
|
|
||||||
index_scheduler: GuardedData<ActionPolicy<{ actions::WEBHOOKS_GET }>, Data<IndexScheduler>>,
|
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
|
||||||
let webhooks = index_scheduler.webhooks();
|
|
||||||
debug!(returns = ?webhooks, "Get webhooks");
|
|
||||||
Ok(HttpResponse::Ok().json(webhooks))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserr, ToSchema)]
|
#[derive(Debug, Deserr, ToSchema)]
|
||||||
@ -105,7 +66,73 @@ struct WebhooksSettings {
|
|||||||
#[schema(value_type = Option<BTreeMap<String, WebhookSettings>>)]
|
#[schema(value_type = Option<BTreeMap<String, WebhookSettings>>)]
|
||||||
#[deserr(default, error = DeserrJsonError<InvalidWebhooks>)]
|
#[deserr(default, error = DeserrJsonError<InvalidWebhooks>)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
webhooks: Setting<BTreeMap<String, Setting<WebhookSettings>>>,
|
webhooks: Setting<BTreeMap<Uuid, Setting<WebhookSettings>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct WebhookWithMetadata {
|
||||||
|
uuid: Uuid,
|
||||||
|
is_editable: bool,
|
||||||
|
#[serde(flatten)]
|
||||||
|
webhook: Webhook,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct WebhookResults {
|
||||||
|
results: Vec<WebhookWithMetadata>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "",
|
||||||
|
tag = "Webhooks",
|
||||||
|
security(("Bearer" = ["webhooks.get", "*.get", "*"])),
|
||||||
|
responses(
|
||||||
|
(status = OK, description = "Webhooks are returned", body = WebhooksSettings, content_type = "application/json", example = json!({
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"url": "https://your.site/on-tasks-completed",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer a-secret-token"
|
||||||
|
},
|
||||||
|
"isEditable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uuid": "550e8400-e29b-41d4-a716-446655440001",
|
||||||
|
"url": "https://another.site/on-tasks-completed",
|
||||||
|
"isEditable": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})),
|
||||||
|
(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_webhooks(
|
||||||
|
index_scheduler: GuardedData<ActionPolicy<{ actions::WEBHOOKS_GET }>, Data<IndexScheduler>>,
|
||||||
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
|
let webhooks = index_scheduler.webhooks();
|
||||||
|
let results = webhooks
|
||||||
|
.webhooks
|
||||||
|
.into_iter()
|
||||||
|
.map(|(uuid, webhook)| WebhookWithMetadata {
|
||||||
|
uuid,
|
||||||
|
is_editable: uuid != Uuid::nil(),
|
||||||
|
webhook,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let results = WebhookResults { results };
|
||||||
|
debug!(returns = ?results, "Get webhooks");
|
||||||
|
Ok(HttpResponse::Ok().json(results))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Default)]
|
#[derive(Serialize, Default)]
|
||||||
@ -115,10 +142,7 @@ pub struct PatchWebhooksAnalytics {
|
|||||||
|
|
||||||
impl PatchWebhooksAnalytics {
|
impl PatchWebhooksAnalytics {
|
||||||
pub fn patch_webhooks() -> Self {
|
pub fn patch_webhooks() -> Self {
|
||||||
PatchWebhooksAnalytics {
|
PatchWebhooksAnalytics { patch_webhooks_count: 1 }
|
||||||
patch_webhooks_count: 1,
|
|
||||||
..Self::default()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,15 +165,15 @@ impl Aggregate for PatchWebhooksAnalytics {
|
|||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
enum WebhooksError {
|
enum WebhooksError {
|
||||||
#[error("The URL for the webhook `{0}` is missing.")]
|
#[error("The URL for the webhook `{0}` is missing.")]
|
||||||
MissingUrl(String),
|
MissingUrl(Uuid),
|
||||||
#[error("Defining too many webhooks would crush the server. Please limit the number of webhooks to 20. You may use a third-party proxy server to dispatch events to more than 20 endpoints.")]
|
#[error("Defining too many webhooks would crush the server. Please limit the number of webhooks to 20. You may use a third-party proxy server to dispatch events to more than 20 endpoints.")]
|
||||||
TooManyWebhooks,
|
TooManyWebhooks,
|
||||||
#[error("Too many headers for the webhook `{0}`. Please limit the number of headers to 200.")]
|
#[error("Too many headers for the webhook `{0}`. Please limit the number of headers to 200.")]
|
||||||
TooManyHeaders(String),
|
TooManyHeaders(Uuid),
|
||||||
#[error("Cannot edit webhook `{0}`. Webhooks prefixed with an underscore are reserved and may not be modified using the API.")]
|
#[error("Cannot edit webhook `{0}`. Webhooks prefixed with an underscore are reserved and may not be modified using the API.")]
|
||||||
ReservedWebhook(String),
|
ReservedWebhook(Uuid),
|
||||||
#[error("Webhook `{0}` not found.")]
|
#[error("Webhook `{0}` not found.")]
|
||||||
WebhookNotFound(String),
|
WebhookNotFound(Uuid),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ErrorCode for WebhooksError {
|
impl ErrorCode for WebhooksError {
|
||||||
@ -175,10 +199,10 @@ impl ErrorCode for WebhooksError {
|
|||||||
responses(
|
responses(
|
||||||
(status = 200, description = "Returns the updated webhooks", body = WebhooksSettings, content_type = "application/json", example = json!({
|
(status = 200, description = "Returns the updated webhooks", body = WebhooksSettings, content_type = "application/json", example = json!({
|
||||||
"webhooks": {
|
"webhooks": {
|
||||||
"name": {
|
"550e8400-e29b-41d4-a716-446655440000": {
|
||||||
"url": "http://example.com/webhook",
|
"url": "http://example.com/webhook",
|
||||||
},
|
},
|
||||||
"anotherName": {
|
"550e8400-e29b-41d4-a716-446655440001": {
|
||||||
"url": "https://your.site/on-tasks-completed",
|
"url": "https://your.site/on-tasks-completed",
|
||||||
"headers": {
|
"headers": {
|
||||||
"Authorization": "Bearer a-secret-token"
|
"Authorization": "Bearer a-secret-token"
|
||||||
@ -212,7 +236,7 @@ fn patch_webhooks_inner(
|
|||||||
new_webhooks: WebhooksSettings,
|
new_webhooks: WebhooksSettings,
|
||||||
) -> Result<Webhooks, ResponseError> {
|
) -> Result<Webhooks, ResponseError> {
|
||||||
fn merge_webhook(
|
fn merge_webhook(
|
||||||
name: &str,
|
uuid: &Uuid,
|
||||||
old_webhook: Option<Webhook>,
|
old_webhook: Option<Webhook>,
|
||||||
new_webhook: WebhookSettings,
|
new_webhook: WebhookSettings,
|
||||||
) -> Result<Webhook, WebhooksError> {
|
) -> Result<Webhook, WebhooksError> {
|
||||||
@ -221,8 +245,8 @@ fn patch_webhooks_inner(
|
|||||||
|
|
||||||
let url = match new_webhook.url {
|
let url = match new_webhook.url {
|
||||||
Setting::Set(url) => url,
|
Setting::Set(url) => url,
|
||||||
Setting::NotSet => old_url.ok_or_else(|| WebhooksError::MissingUrl(name.to_owned()))?,
|
Setting::NotSet => old_url.ok_or_else(|| WebhooksError::MissingUrl(uuid.to_owned()))?,
|
||||||
Setting::Reset => return Err(WebhooksError::MissingUrl(name.to_owned())),
|
Setting::Reset => return Err(WebhooksError::MissingUrl(uuid.to_owned())),
|
||||||
};
|
};
|
||||||
|
|
||||||
let headers = match new_webhook.headers {
|
let headers = match new_webhook.headers {
|
||||||
@ -246,7 +270,7 @@ fn patch_webhooks_inner(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if headers.len() > 200 {
|
if headers.len() > 200 {
|
||||||
return Err(WebhooksError::TooManyHeaders(name.to_owned()));
|
return Err(WebhooksError::TooManyHeaders(uuid.to_owned()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Webhook { url, headers })
|
Ok(Webhook { url, headers })
|
||||||
@ -258,19 +282,19 @@ fn patch_webhooks_inner(
|
|||||||
|
|
||||||
match new_webhooks.webhooks {
|
match new_webhooks.webhooks {
|
||||||
Setting::Set(new_webhooks) => {
|
Setting::Set(new_webhooks) => {
|
||||||
for (name, new_webhook) in new_webhooks {
|
for (uuid, new_webhook) in new_webhooks {
|
||||||
if name.starts_with('_') {
|
if uuid.is_nil() {
|
||||||
return Err(WebhooksError::ReservedWebhook(name).into());
|
return Err(WebhooksError::ReservedWebhook(uuid).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
match new_webhook {
|
match new_webhook {
|
||||||
Setting::Set(new_webhook) => {
|
Setting::Set(new_webhook) => {
|
||||||
let old_webhook = webhooks.remove(&name);
|
let old_webhook = webhooks.remove(&uuid);
|
||||||
let webhook = merge_webhook(&name, old_webhook, new_webhook)?;
|
let webhook = merge_webhook(&uuid, old_webhook, new_webhook)?;
|
||||||
webhooks.insert(name.clone(), webhook);
|
webhooks.insert(uuid, webhook);
|
||||||
}
|
}
|
||||||
Setting::Reset => {
|
Setting::Reset => {
|
||||||
webhooks.remove(&name);
|
webhooks.remove(&uuid);
|
||||||
}
|
}
|
||||||
Setting::NotSet => (),
|
Setting::NotSet => (),
|
||||||
}
|
}
|
||||||
@ -298,25 +322,34 @@ fn patch_webhooks_inner(
|
|||||||
tag = "Webhooks",
|
tag = "Webhooks",
|
||||||
security(("Bearer" = ["webhooks.get", "*.get", "*"])),
|
security(("Bearer" = ["webhooks.get", "*.get", "*"])),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, description = "Webhook found", body = WebhookSettings, content_type = "application/json"),
|
(status = 200, description = "Webhook found", body = WebhookSettings, content_type = "application/json", example = json!({
|
||||||
|
"uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"url": "https://your.site/on-tasks-completed",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer a-secret"
|
||||||
|
},
|
||||||
|
"isEditable": true
|
||||||
|
})),
|
||||||
(status = 404, description = "Webhook not found", body = ResponseError, content_type = "application/json"),
|
(status = 404, description = "Webhook not found", body = ResponseError, content_type = "application/json"),
|
||||||
(status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json"),
|
(status = 401, description = "The authorization header is missing", body = ResponseError, content_type = "application/json"),
|
||||||
),
|
),
|
||||||
params(
|
params(
|
||||||
("name" = String, Path, description = "The name of the webhook")
|
("uuid" = Uuid, Path, description = "The universally unique identifier of the webhook")
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
async fn get_webhook(
|
async fn get_webhook(
|
||||||
index_scheduler: GuardedData<ActionPolicy<{ actions::WEBHOOKS_GET }>, Data<IndexScheduler>>,
|
index_scheduler: GuardedData<ActionPolicy<{ actions::WEBHOOKS_GET }>, Data<IndexScheduler>>,
|
||||||
name: Path<String>,
|
uuid: Path<Uuid>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
let webhook_name = name.into_inner();
|
let uuid = uuid.into_inner();
|
||||||
let webhooks = index_scheduler.webhooks();
|
let mut webhooks = index_scheduler.webhooks();
|
||||||
|
|
||||||
if let Some(webhook) = webhooks.webhooks.get(&webhook_name) {
|
let webhook = webhooks.webhooks.remove(&uuid).ok_or(WebhooksError::WebhookNotFound(uuid))?;
|
||||||
debug!(returns = ?webhook, "Get webhook {}", webhook_name);
|
|
||||||
Ok(HttpResponse::Ok().json(webhook))
|
debug!(returns = ?webhook, "Get webhook {}", uuid);
|
||||||
} else {
|
Ok(HttpResponse::Ok().json(WebhookWithMetadata {
|
||||||
Err(WebhooksError::WebhookNotFound(webhook_name).into())
|
uuid,
|
||||||
}
|
is_editable: uuid != Uuid::nil(),
|
||||||
|
webhook,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
@ -145,13 +145,14 @@ async fn cli_only() {
|
|||||||
server_handle.abort();
|
server_handle.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[actix_web::test]
|
#[actix_web::test]
|
||||||
async fn cli_with_dumps() {
|
async fn cli_with_dumps() {
|
||||||
let db_path = tempfile::tempdir().unwrap();
|
let db_path = tempfile::tempdir().unwrap();
|
||||||
let server = Server::new_with_options(Opt {
|
let server = Server::new_with_options(Opt {
|
||||||
task_webhook_url: Some(Url::parse("http://defined-in-test-cli.com").unwrap()),
|
task_webhook_url: Some(Url::parse("http://defined-in-test-cli.com").unwrap()),
|
||||||
task_webhook_authorization_header: Some(String::from("Bearer a-secret-token-defined-in-test-cli")),
|
task_webhook_authorization_header: Some(String::from(
|
||||||
|
"Bearer a-secret-token-defined-in-test-cli",
|
||||||
|
)),
|
||||||
import_dump: Some(PathBuf::from("../dump/tests/assets/v6-with-webhooks.dump")),
|
import_dump: Some(PathBuf::from("../dump/tests/assets/v6-with-webhooks.dump")),
|
||||||
..default_settings(db_path.path())
|
..default_settings(db_path.path())
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user