mirror of
https://github.com/meilisearch/meilisearch.git
synced 2024-11-26 20:15:07 +08:00
Merge #228
228: Authentication rework r=curquiza a=MarinPostma In an attempt to fix #201, I ended up rewriting completely the authentication system we use. This is because actix doesn't allow to wrap a single route into a middleware, so we initially put each route into it's own service to use the authentication middleware. Routes are now grouped in resources, fixing #201. As for the authentication, I decided to take a very different approach, and ditch middleware altogether. Instead, I decided to use actix's [extractor](https://actix.rs/docs/extractors/). `Data` is now wrapped in a `GuardedData<P: Policy, T>` (where `T` is `Data`) in each route. The `Policy` trait, thanks to the `authenticate` method tell if a request is authorized to access the resources in the route. Concretely, before the server starts, it is configured with a `AuthConfig` instance that can either be `AuthConfig::NoAuth` when no auth is required at runtime, or `AuthConfig::Auth(Policies)`, where `Policies` maps the `Policy` type to it singleton instance. In the current implementation, and this to match the legacy meilisearch behaviour, each policy implementation contains a `HashSet` of token (`Vec<u8>` for now), that represents the user it can authenticate. When starting the program, each key (identified as a user) is given a set of `Policy`, representing its roles. The later is facilitated by the `create_users` macro, like so: ```rust create_users!( policies, master_key.as_bytes() => { Admin, Private, Public }, private_key.as_bytes() => { Private, Public }, public_key.as_bytes() => { Public } ); ``` This is some groundwork for later development on a full fledged authentication system for meilisearch. fix #201 Co-authored-by: marin postma <postma.marin@protonmail.com>
This commit is contained in:
commit
d7ca68d8e9
@ -10,23 +10,6 @@ use meilisearch_error::{Code, ErrorCode};
|
|||||||
use milli::UserError;
|
use milli::UserError;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
pub enum AuthenticationError {
|
|
||||||
#[error("You must have an authorization token")]
|
|
||||||
MissingAuthorizationHeader,
|
|
||||||
#[error("Invalid API key")]
|
|
||||||
InvalidToken(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ErrorCode for AuthenticationError {
|
|
||||||
fn error_code(&self) -> Code {
|
|
||||||
match self {
|
|
||||||
AuthenticationError::MissingAuthorizationHeader => Code::MissingAuthorizationHeader,
|
|
||||||
AuthenticationError::InvalidToken(_) => Code::InvalidToken,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ResponseError {
|
pub struct ResponseError {
|
||||||
|
26
meilisearch-http/src/extractors/authentication/error.rs
Normal file
26
meilisearch-http/src/extractors/authentication/error.rs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
use meilisearch_error::{Code, ErrorCode};
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum AuthenticationError {
|
||||||
|
#[error("You must have an authorization token")]
|
||||||
|
MissingAuthorizationHeader,
|
||||||
|
#[error("Invalid API key")]
|
||||||
|
InvalidToken(String),
|
||||||
|
// Triggered on configuration error.
|
||||||
|
#[error("Irretrievable state")]
|
||||||
|
IrretrievableState,
|
||||||
|
#[error("Unknown authentication policy")]
|
||||||
|
UnknownPolicy,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorCode for AuthenticationError {
|
||||||
|
fn error_code(&self) -> Code {
|
||||||
|
match self {
|
||||||
|
AuthenticationError::MissingAuthorizationHeader => Code::MissingAuthorizationHeader,
|
||||||
|
AuthenticationError::InvalidToken(_) => Code::InvalidToken,
|
||||||
|
AuthenticationError::IrretrievableState => Code::Internal,
|
||||||
|
AuthenticationError::UnknownPolicy => Code::Internal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
182
meilisearch-http/src/extractors/authentication/mod.rs
Normal file
182
meilisearch-http/src/extractors/authentication/mod.rs
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
mod error;
|
||||||
|
|
||||||
|
use std::any::{Any, TypeId};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use actix_web::FromRequest;
|
||||||
|
use futures::future::err;
|
||||||
|
use futures::future::{ok, Ready};
|
||||||
|
|
||||||
|
use crate::error::ResponseError;
|
||||||
|
use error::AuthenticationError;
|
||||||
|
|
||||||
|
macro_rules! create_policies {
|
||||||
|
($($name:ident), *) => {
|
||||||
|
pub mod policies {
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use crate::extractors::authentication::Policy;
|
||||||
|
|
||||||
|
$(
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct $name {
|
||||||
|
inner: HashSet<Vec<u8>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $name {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { inner: HashSet::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, token: Vec<u8>) {
|
||||||
|
&mut self.inner.insert(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Policy for $name {
|
||||||
|
fn authenticate(&self, token: &[u8]) -> bool {
|
||||||
|
self.inner.contains(token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
create_policies!(Public, Private, Admin);
|
||||||
|
|
||||||
|
/// Instanciate a `Policies`, filled with the given policies.
|
||||||
|
macro_rules! init_policies {
|
||||||
|
($($name:ident), *) => {
|
||||||
|
{
|
||||||
|
let mut policies = crate::extractors::authentication::Policies::new();
|
||||||
|
$(
|
||||||
|
let policy = $name::new();
|
||||||
|
policies.insert(policy);
|
||||||
|
)*
|
||||||
|
policies
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds user to all specified policies.
|
||||||
|
macro_rules! create_users {
|
||||||
|
($policies:ident, $($user:expr => { $($policy:ty), * }), *) => {
|
||||||
|
{
|
||||||
|
$(
|
||||||
|
$(
|
||||||
|
$policies.get_mut::<$policy>().map(|p| p.add($user.to_owned()));
|
||||||
|
)*
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GuardedData<T, D> {
|
||||||
|
data: D,
|
||||||
|
_marker: PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, D> Deref for GuardedData<T, D> {
|
||||||
|
type Target = D;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Policy {
|
||||||
|
fn authenticate(&self, token: &[u8]) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Policies {
|
||||||
|
inner: HashMap<TypeId, Box<dyn Any>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Policies {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert<S: Policy + 'static>(&mut self, policy: S) {
|
||||||
|
self.inner.insert(TypeId::of::<S>(), Box::new(policy));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get<S: Policy + 'static>(&self) -> Option<&S> {
|
||||||
|
self.inner
|
||||||
|
.get(&TypeId::of::<S>())
|
||||||
|
.and_then(|p| p.downcast_ref::<S>())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mut<S: Policy + 'static>(&mut self) -> Option<&mut S> {
|
||||||
|
self.inner
|
||||||
|
.get_mut(&TypeId::of::<S>())
|
||||||
|
.and_then(|p| p.downcast_mut::<S>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Policies {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum AuthConfig {
|
||||||
|
NoAuth,
|
||||||
|
Auth(Policies),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AuthConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::NoAuth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Policy + 'static, D: 'static + Clone> FromRequest for GuardedData<P, D> {
|
||||||
|
type Config = AuthConfig;
|
||||||
|
|
||||||
|
type Error = ResponseError;
|
||||||
|
|
||||||
|
type Future = Ready<Result<Self, Self::Error>>;
|
||||||
|
|
||||||
|
fn from_request(
|
||||||
|
req: &actix_web::HttpRequest,
|
||||||
|
_payload: &mut actix_http::Payload,
|
||||||
|
) -> Self::Future {
|
||||||
|
match req.app_data::<Self::Config>() {
|
||||||
|
Some(config) => match config {
|
||||||
|
AuthConfig::NoAuth => match req.app_data::<D>().cloned() {
|
||||||
|
Some(data) => ok(Self {
|
||||||
|
data,
|
||||||
|
_marker: PhantomData,
|
||||||
|
}),
|
||||||
|
None => err(AuthenticationError::IrretrievableState.into()),
|
||||||
|
},
|
||||||
|
AuthConfig::Auth(policies) => match policies.get::<P>() {
|
||||||
|
Some(policy) => match req.headers().get("x-meili-api-key") {
|
||||||
|
Some(token) => {
|
||||||
|
if policy.authenticate(token.as_bytes()) {
|
||||||
|
match req.app_data::<D>().cloned() {
|
||||||
|
Some(data) => ok(Self {
|
||||||
|
data,
|
||||||
|
_marker: PhantomData,
|
||||||
|
}),
|
||||||
|
None => err(AuthenticationError::IrretrievableState.into()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err(AuthenticationError::InvalidToken(String::from("hello")).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => err(AuthenticationError::MissingAuthorizationHeader.into()),
|
||||||
|
},
|
||||||
|
None => err(AuthenticationError::UnknownPolicy.into()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
None => err(AuthenticationError::IrretrievableState.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1,3 @@
|
|||||||
pub mod payload;
|
pub mod payload;
|
||||||
|
#[macro_use]
|
||||||
|
pub mod authentication;
|
||||||
|
@ -1,150 +0,0 @@
|
|||||||
use std::pin::Pin;
|
|
||||||
use std::task::{Context, Poll};
|
|
||||||
|
|
||||||
use actix_web::body::Body;
|
|
||||||
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
|
|
||||||
use actix_web::web;
|
|
||||||
use actix_web::ResponseError as _;
|
|
||||||
use futures::future::{ok, Future, Ready};
|
|
||||||
use futures::ready;
|
|
||||||
use pin_project::pin_project;
|
|
||||||
|
|
||||||
use crate::error::{AuthenticationError, ResponseError};
|
|
||||||
use crate::Data;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub enum Authentication {
|
|
||||||
Public,
|
|
||||||
Private,
|
|
||||||
Admin,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S: 'static> Transform<S, ServiceRequest> for Authentication
|
|
||||||
where
|
|
||||||
S: Service<ServiceRequest, Response = ServiceResponse<Body>, Error = actix_web::Error>,
|
|
||||||
{
|
|
||||||
type Response = ServiceResponse<Body>;
|
|
||||||
type Error = actix_web::Error;
|
|
||||||
type InitError = ();
|
|
||||||
type Transform = LoggingMiddleware<S>;
|
|
||||||
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
|
||||||
|
|
||||||
fn new_transform(&self, service: S) -> Self::Future {
|
|
||||||
ok(LoggingMiddleware {
|
|
||||||
acl: *self,
|
|
||||||
service,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct LoggingMiddleware<S> {
|
|
||||||
acl: Authentication,
|
|
||||||
service: S,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
impl<S> Service<ServiceRequest> for LoggingMiddleware<S>
|
|
||||||
where
|
|
||||||
S: Service<ServiceRequest, Response = ServiceResponse<Body>, Error = actix_web::Error>,
|
|
||||||
{
|
|
||||||
type Response = ServiceResponse<Body>;
|
|
||||||
type Error = actix_web::Error;
|
|
||||||
type Future = AuthenticationFuture<S>;
|
|
||||||
|
|
||||||
fn poll_ready(&self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
|
|
||||||
self.service.poll_ready(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(&self, req: ServiceRequest) -> Self::Future {
|
|
||||||
let data = req.app_data::<web::Data<Data>>().unwrap();
|
|
||||||
|
|
||||||
if data.api_keys().master.is_none() {
|
|
||||||
return AuthenticationFuture::Authenticated(self.service.call(req));
|
|
||||||
}
|
|
||||||
|
|
||||||
let auth_header = match req.headers().get("X-Meili-API-Key") {
|
|
||||||
Some(auth) => match auth.to_str() {
|
|
||||||
Ok(auth) => auth,
|
|
||||||
Err(_) => return AuthenticationFuture::NoHeader(Some(req)),
|
|
||||||
},
|
|
||||||
None => return AuthenticationFuture::NoHeader(Some(req)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let authenticated = match self.acl {
|
|
||||||
Authentication::Admin => data.api_keys().master.as_deref() == Some(auth_header),
|
|
||||||
Authentication::Private => {
|
|
||||||
data.api_keys().master.as_deref() == Some(auth_header)
|
|
||||||
|| data.api_keys().private.as_deref() == Some(auth_header)
|
|
||||||
}
|
|
||||||
Authentication::Public => {
|
|
||||||
data.api_keys().master.as_deref() == Some(auth_header)
|
|
||||||
|| data.api_keys().private.as_deref() == Some(auth_header)
|
|
||||||
|| data.api_keys().public.as_deref() == Some(auth_header)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if authenticated {
|
|
||||||
AuthenticationFuture::Authenticated(self.service.call(req))
|
|
||||||
} else {
|
|
||||||
AuthenticationFuture::Refused(Some(req))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pin_project(project = AuthProj)]
|
|
||||||
pub enum AuthenticationFuture<S>
|
|
||||||
where
|
|
||||||
S: Service<ServiceRequest>,
|
|
||||||
{
|
|
||||||
Authenticated(#[pin] S::Future),
|
|
||||||
NoHeader(Option<ServiceRequest>),
|
|
||||||
Refused(Option<ServiceRequest>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> Future for AuthenticationFuture<S>
|
|
||||||
where
|
|
||||||
S: Service<ServiceRequest, Response = ServiceResponse<Body>, Error = actix_web::Error>,
|
|
||||||
{
|
|
||||||
type Output = Result<ServiceResponse<Body>, actix_web::Error>;
|
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
|
||||||
let this = self.project();
|
|
||||||
match this {
|
|
||||||
AuthProj::Authenticated(fut) => match ready!(fut.poll(cx)) {
|
|
||||||
Ok(resp) => Poll::Ready(Ok(resp)),
|
|
||||||
Err(e) => Poll::Ready(Err(e)),
|
|
||||||
},
|
|
||||||
AuthProj::NoHeader(req) => {
|
|
||||||
match req.take() {
|
|
||||||
Some(req) => {
|
|
||||||
let response =
|
|
||||||
ResponseError::from(AuthenticationError::MissingAuthorizationHeader);
|
|
||||||
let response = response.error_response();
|
|
||||||
let response = req.into_response(response);
|
|
||||||
Poll::Ready(Ok(response))
|
|
||||||
}
|
|
||||||
// https://doc.rust-lang.org/nightly/std/future/trait.Future.html#panics
|
|
||||||
None => unreachable!("poll called again on ready future"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AuthProj::Refused(req) => {
|
|
||||||
match req.take() {
|
|
||||||
Some(req) => {
|
|
||||||
let bad_token = req
|
|
||||||
.headers()
|
|
||||||
.get("X-Meili-API-Key")
|
|
||||||
.map(|h| h.to_str().map(String::from).unwrap_or_default())
|
|
||||||
.unwrap_or_default();
|
|
||||||
let response =
|
|
||||||
ResponseError::from(AuthenticationError::InvalidToken(bad_token));
|
|
||||||
let response = response.error_response();
|
|
||||||
let response = req.into_response(response);
|
|
||||||
Poll::Ready(Ok(response))
|
|
||||||
}
|
|
||||||
// https://doc.rust-lang.org/nightly/std/future/trait.Future.html#panics
|
|
||||||
None => unreachable!("poll called again on ready future"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,4 @@
|
|||||||
pub mod authentication;
|
|
||||||
pub mod compression;
|
pub mod compression;
|
||||||
mod env;
|
mod env;
|
||||||
|
|
||||||
pub use authentication::Authentication;
|
|
||||||
pub use env::EnvSizer;
|
pub use env::EnvSizer;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
pub mod data;
|
pub mod data;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
#[macro_use]
|
||||||
pub mod extractors;
|
pub mod extractors;
|
||||||
pub mod helpers;
|
pub mod helpers;
|
||||||
mod index;
|
mod index;
|
||||||
@ -11,17 +12,21 @@ pub mod routes;
|
|||||||
#[cfg(all(not(debug_assertions), feature = "analytics"))]
|
#[cfg(all(not(debug_assertions), feature = "analytics"))]
|
||||||
pub mod analytics;
|
pub mod analytics;
|
||||||
|
|
||||||
|
use crate::extractors::authentication::AuthConfig;
|
||||||
|
|
||||||
pub use self::data::Data;
|
pub use self::data::Data;
|
||||||
pub use option::Opt;
|
pub use option::Opt;
|
||||||
|
|
||||||
use actix_web::web;
|
use actix_web::web;
|
||||||
|
|
||||||
|
use extractors::authentication::policies::*;
|
||||||
use extractors::payload::PayloadConfig;
|
use extractors::payload::PayloadConfig;
|
||||||
|
|
||||||
pub fn configure_data(config: &mut web::ServiceConfig, data: Data) {
|
pub fn configure_data(config: &mut web::ServiceConfig, data: Data) {
|
||||||
let http_payload_size_limit = data.http_payload_size_limit();
|
let http_payload_size_limit = data.http_payload_size_limit();
|
||||||
config
|
config
|
||||||
.data(data)
|
.data(data.clone())
|
||||||
|
.app_data(data)
|
||||||
.app_data(
|
.app_data(
|
||||||
web::JsonConfig::default()
|
web::JsonConfig::default()
|
||||||
.limit(http_payload_size_limit)
|
.limit(http_payload_size_limit)
|
||||||
@ -35,10 +40,30 @@ pub fn configure_data(config: &mut web::ServiceConfig, data: Data) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn configure_auth(config: &mut web::ServiceConfig, data: &Data) {
|
||||||
|
let keys = data.api_keys();
|
||||||
|
let auth_config = if let Some(ref master_key) = keys.master {
|
||||||
|
let private_key = keys.private.as_ref().unwrap();
|
||||||
|
let public_key = keys.public.as_ref().unwrap();
|
||||||
|
let mut policies = init_policies!(Public, Private, Admin);
|
||||||
|
create_users!(
|
||||||
|
policies,
|
||||||
|
master_key.as_bytes() => { Admin, Private, Public },
|
||||||
|
private_key.as_bytes() => { Private, Public },
|
||||||
|
public_key.as_bytes() => { Public }
|
||||||
|
);
|
||||||
|
AuthConfig::Auth(policies)
|
||||||
|
} else {
|
||||||
|
AuthConfig::NoAuth
|
||||||
|
};
|
||||||
|
|
||||||
|
config.app_data(auth_config);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "mini-dashboard")]
|
#[cfg(feature = "mini-dashboard")]
|
||||||
pub fn dashboard(config: &mut web::ServiceConfig, enable_frontend: bool) {
|
pub fn dashboard(config: &mut web::ServiceConfig, enable_frontend: bool) {
|
||||||
use actix_web_static_files::Resource;
|
|
||||||
use actix_web::HttpResponse;
|
use actix_web::HttpResponse;
|
||||||
|
use actix_web_static_files::Resource;
|
||||||
|
|
||||||
mod generated {
|
mod generated {
|
||||||
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
|
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
|
||||||
@ -46,30 +71,32 @@ pub fn dashboard(config: &mut web::ServiceConfig, enable_frontend: bool) {
|
|||||||
|
|
||||||
if enable_frontend {
|
if enable_frontend {
|
||||||
let generated = generated::generate();
|
let generated = generated::generate();
|
||||||
let mut scope = web::scope("/");
|
let mut scope = web::scope("/");
|
||||||
// Generate routes for mini-dashboard assets
|
// Generate routes for mini-dashboard assets
|
||||||
for (path, resource) in generated.into_iter() {
|
for (path, resource) in generated.into_iter() {
|
||||||
let Resource {mime_type, data, ..} = resource;
|
let Resource {
|
||||||
// Redirect index.html to /
|
mime_type, data, ..
|
||||||
if path == "index.html" {
|
} = resource;
|
||||||
config.service(web::resource("/").route(web::get().to(move || {
|
// Redirect index.html to /
|
||||||
HttpResponse::Ok().content_type(mime_type).body(data)
|
if path == "index.html" {
|
||||||
})));
|
config.service(web::resource("/").route(
|
||||||
} else {
|
web::get().to(move || HttpResponse::Ok().content_type(mime_type).body(data)),
|
||||||
scope = scope.service(web::resource(path).route(web::get().to(move || {
|
));
|
||||||
HttpResponse::Ok().content_type(mime_type).body(data)
|
} else {
|
||||||
})));
|
scope = scope.service(web::resource(path).route(
|
||||||
}
|
web::get().to(move || HttpResponse::Ok().content_type(mime_type).body(data)),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
config.service(scope);
|
}
|
||||||
|
config.service(scope);
|
||||||
} else {
|
} else {
|
||||||
config.service(routes::running);
|
config.service(web::resource("/").route(web::get().to(routes::running)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "mini-dashboard"))]
|
#[cfg(not(feature = "mini-dashboard"))]
|
||||||
pub fn dashboard(config: &mut web::ServiceConfig, _enable_frontend: bool) {
|
pub fn dashboard(config: &mut web::ServiceConfig, _enable_frontend: bool) {
|
||||||
config.service(routes::running);
|
config.service(web::resource("/").route(web::get().to(routes::running)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
@ -80,10 +107,11 @@ macro_rules! create_app {
|
|||||||
use actix_web::App;
|
use actix_web::App;
|
||||||
use actix_web::{middleware, web};
|
use actix_web::{middleware, web};
|
||||||
use meilisearch_http::routes::*;
|
use meilisearch_http::routes::*;
|
||||||
use meilisearch_http::{configure_data, dashboard};
|
use meilisearch_http::{configure_auth, configure_data, dashboard};
|
||||||
|
|
||||||
App::new()
|
App::new()
|
||||||
.configure(|s| configure_data(s, $data.clone()))
|
.configure(|s| configure_data(s, $data.clone()))
|
||||||
|
.configure(|s| configure_auth(s, &$data))
|
||||||
.configure(document::services)
|
.configure(document::services)
|
||||||
.configure(index::services)
|
.configure(index::services)
|
||||||
.configure(search::services)
|
.configure(search::services)
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
use actix_web::{delete, get, post, put};
|
|
||||||
use actix_web::{web, HttpResponse};
|
use actix_web::{web, HttpResponse};
|
||||||
use indexmap::IndexMap;
|
use log::debug;
|
||||||
use log::{debug, error};
|
|
||||||
use milli::update::{IndexDocumentsMethod, UpdateFormat};
|
use milli::update::{IndexDocumentsMethod, UpdateFormat};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::error::ResponseError;
|
use crate::error::ResponseError;
|
||||||
|
use crate::extractors::authentication::{policies::*, GuardedData};
|
||||||
use crate::extractors::payload::Payload;
|
use crate::extractors::payload::Payload;
|
||||||
use crate::helpers::Authentication;
|
|
||||||
use crate::routes::IndexParam;
|
use crate::routes::IndexParam;
|
||||||
use crate::Data;
|
use crate::Data;
|
||||||
|
|
||||||
@ -17,7 +15,6 @@ const DEFAULT_RETRIEVE_DOCUMENTS_LIMIT: usize = 20;
|
|||||||
|
|
||||||
macro_rules! guard_content_type {
|
macro_rules! guard_content_type {
|
||||||
($fn_name:ident, $guard_value:literal) => {
|
($fn_name:ident, $guard_value:literal) => {
|
||||||
#[allow(dead_code)]
|
|
||||||
fn $fn_name(head: &actix_web::dev::RequestHead) -> bool {
|
fn $fn_name(head: &actix_web::dev::RequestHead) -> bool {
|
||||||
if let Some(content_type) = head.headers.get("Content-Type") {
|
if let Some(content_type) = head.headers.get("Content-Type") {
|
||||||
content_type
|
content_type
|
||||||
@ -33,8 +30,6 @@ macro_rules! guard_content_type {
|
|||||||
|
|
||||||
guard_content_type!(guard_json, "application/json");
|
guard_content_type!(guard_json, "application/json");
|
||||||
|
|
||||||
type Document = IndexMap<String, Value>;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct DocumentParam {
|
struct DocumentParam {
|
||||||
index_uid: String,
|
index_uid: String,
|
||||||
@ -42,21 +37,27 @@ struct DocumentParam {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(get_document)
|
cfg.service(
|
||||||
.service(delete_document)
|
web::scope("/indexes/{index_uid}/documents")
|
||||||
.service(get_all_documents)
|
.service(
|
||||||
.service(add_documents)
|
web::resource("")
|
||||||
.service(update_documents)
|
.route(web::get().to(get_all_documents))
|
||||||
.service(delete_documents)
|
.route(web::post().guard(guard_json).to(add_documents))
|
||||||
.service(clear_all_documents);
|
.route(web::put().guard(guard_json).to(update_documents))
|
||||||
|
.route(web::delete().to(clear_all_documents)),
|
||||||
|
)
|
||||||
|
// this route needs to be before the /documents/{document_id} to match properly
|
||||||
|
.service(web::resource("/delete-batch").route(web::post().to(delete_documents)))
|
||||||
|
.service(
|
||||||
|
web::resource("/{document_id}")
|
||||||
|
.route(web::get().to(get_document))
|
||||||
|
.route(web::delete().to(delete_document)),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get(
|
|
||||||
"/indexes/{index_uid}/documents/{document_id}",
|
|
||||||
wrap = "Authentication::Public"
|
|
||||||
)]
|
|
||||||
async fn get_document(
|
async fn get_document(
|
||||||
data: web::Data<Data>,
|
data: GuardedData<Public, Data>,
|
||||||
path: web::Path<DocumentParam>,
|
path: web::Path<DocumentParam>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
let index = path.index_uid.clone();
|
let index = path.index_uid.clone();
|
||||||
@ -68,12 +69,8 @@ async fn get_document(
|
|||||||
Ok(HttpResponse::Ok().json(document))
|
Ok(HttpResponse::Ok().json(document))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[delete(
|
|
||||||
"/indexes/{index_uid}/documents/{document_id}",
|
|
||||||
wrap = "Authentication::Private"
|
|
||||||
)]
|
|
||||||
async fn delete_document(
|
async fn delete_document(
|
||||||
data: web::Data<Data>,
|
data: GuardedData<Private, Data>,
|
||||||
path: web::Path<DocumentParam>,
|
path: web::Path<DocumentParam>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
let update_status = data
|
let update_status = data
|
||||||
@ -91,9 +88,8 @@ struct BrowseQuery {
|
|||||||
attributes_to_retrieve: Option<String>,
|
attributes_to_retrieve: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/indexes/{index_uid}/documents", wrap = "Authentication::Public")]
|
|
||||||
async fn get_all_documents(
|
async fn get_all_documents(
|
||||||
data: web::Data<Data>,
|
data: GuardedData<Public, Data>,
|
||||||
path: web::Path<IndexParam>,
|
path: web::Path<IndexParam>,
|
||||||
params: web::Query<BrowseQuery>,
|
params: web::Query<BrowseQuery>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
@ -129,9 +125,8 @@ struct UpdateDocumentsQuery {
|
|||||||
|
|
||||||
/// Route used when the payload type is "application/json"
|
/// Route used when the payload type is "application/json"
|
||||||
/// Used to add or replace documents
|
/// Used to add or replace documents
|
||||||
#[post("/indexes/{index_uid}/documents", wrap = "Authentication::Private")]
|
|
||||||
async fn add_documents(
|
async fn add_documents(
|
||||||
data: web::Data<Data>,
|
data: GuardedData<Private, Data>,
|
||||||
path: web::Path<IndexParam>,
|
path: web::Path<IndexParam>,
|
||||||
params: web::Query<UpdateDocumentsQuery>,
|
params: web::Query<UpdateDocumentsQuery>,
|
||||||
body: Payload,
|
body: Payload,
|
||||||
@ -151,33 +146,8 @@ async fn add_documents(
|
|||||||
Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() })))
|
Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() })))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Default route for adding documents, this should return an error and redirect to the documentation
|
|
||||||
#[post("/indexes/{index_uid}/documents", wrap = "Authentication::Private")]
|
|
||||||
async fn add_documents_default(
|
|
||||||
_data: web::Data<Data>,
|
|
||||||
_path: web::Path<IndexParam>,
|
|
||||||
_params: web::Query<UpdateDocumentsQuery>,
|
|
||||||
_body: web::Json<Vec<Document>>,
|
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
|
||||||
error!("Unknown document type");
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Default route for adding documents, this should return an error and redirect to the documentation
|
|
||||||
#[put("/indexes/{index_uid}/documents", wrap = "Authentication::Private")]
|
|
||||||
async fn update_documents_default(
|
|
||||||
_data: web::Data<Data>,
|
|
||||||
_path: web::Path<IndexParam>,
|
|
||||||
_params: web::Query<UpdateDocumentsQuery>,
|
|
||||||
_body: web::Json<Vec<Document>>,
|
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
|
||||||
error!("Unknown document type");
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[put("/indexes/{index_uid}/documents", wrap = "Authentication::Private")]
|
|
||||||
async fn update_documents(
|
async fn update_documents(
|
||||||
data: web::Data<Data>,
|
data: GuardedData<Private, Data>,
|
||||||
path: web::Path<IndexParam>,
|
path: web::Path<IndexParam>,
|
||||||
params: web::Query<UpdateDocumentsQuery>,
|
params: web::Query<UpdateDocumentsQuery>,
|
||||||
body: Payload,
|
body: Payload,
|
||||||
@ -197,12 +167,8 @@ async fn update_documents(
|
|||||||
Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update.id() })))
|
Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update.id() })))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post(
|
|
||||||
"/indexes/{index_uid}/documents/delete-batch",
|
|
||||||
wrap = "Authentication::Private"
|
|
||||||
)]
|
|
||||||
async fn delete_documents(
|
async fn delete_documents(
|
||||||
data: web::Data<Data>,
|
data: GuardedData<Private, Data>,
|
||||||
path: web::Path<IndexParam>,
|
path: web::Path<IndexParam>,
|
||||||
body: web::Json<Vec<Value>>,
|
body: web::Json<Vec<Value>>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
@ -221,10 +187,8 @@ async fn delete_documents(
|
|||||||
Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() })))
|
Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() })))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// delete all documents
|
|
||||||
#[delete("/indexes/{index_uid}/documents", wrap = "Authentication::Private")]
|
|
||||||
async fn clear_all_documents(
|
async fn clear_all_documents(
|
||||||
data: web::Data<Data>,
|
data: GuardedData<Private, Data>,
|
||||||
path: web::Path<IndexParam>,
|
path: web::Path<IndexParam>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
let update_status = data.clear_documents(path.index_uid.clone()).await?;
|
let update_status = data.clear_documents(path.index_uid.clone()).await?;
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
use actix_web::HttpResponse;
|
|
||||||
use actix_web::{get, post, web};
|
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
use actix_web::{web, HttpResponse};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::error::ResponseError;
|
use crate::error::ResponseError;
|
||||||
use crate::helpers::Authentication;
|
use crate::extractors::authentication::{policies::*, GuardedData};
|
||||||
use crate::Data;
|
use crate::Data;
|
||||||
|
|
||||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(create_dump).service(get_dump_status);
|
cfg.service(web::resource("/dumps").route(web::post().to(create_dump)))
|
||||||
|
.service(web::resource("/dumps/{dump_uid}/status").route(web::get().to(get_dump_status)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/dumps", wrap = "Authentication::Private")]
|
async fn create_dump(data: GuardedData<Private, Data>) -> Result<HttpResponse, ResponseError> {
|
||||||
async fn create_dump(data: web::Data<Data>) -> Result<HttpResponse, ResponseError> {
|
|
||||||
let res = data.create_dump().await?;
|
let res = data.create_dump().await?;
|
||||||
|
|
||||||
debug!("returns: {:?}", res);
|
debug!("returns: {:?}", res);
|
||||||
@ -30,9 +29,8 @@ struct DumpParam {
|
|||||||
dump_uid: String,
|
dump_uid: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/dumps/{dump_uid}/status", wrap = "Authentication::Private")]
|
|
||||||
async fn get_dump_status(
|
async fn get_dump_status(
|
||||||
data: web::Data<Data>,
|
data: GuardedData<Private, Data>,
|
||||||
path: web::Path<DumpParam>,
|
path: web::Path<DumpParam>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
let res = data.dump_status(path.dump_uid.clone()).await?;
|
let res = data.dump_status(path.dump_uid.clone()).await?;
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
use actix_web::get;
|
|
||||||
use actix_web::{web, HttpResponse};
|
use actix_web::{web, HttpResponse};
|
||||||
|
|
||||||
use crate::error::ResponseError;
|
use crate::error::ResponseError;
|
||||||
|
|
||||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(get_health);
|
cfg.service(web::resource("/health").route(web::get().to(get_health)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/health")]
|
|
||||||
async fn get_health() -> Result<HttpResponse, ResponseError> {
|
async fn get_health() -> Result<HttpResponse, ResponseError> {
|
||||||
Ok(HttpResponse::Ok().json(serde_json::json!({ "status": "available" })))
|
Ok(HttpResponse::Ok().json(serde_json::json!({ "status": "available" })))
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use actix_web::{delete, get, post, put};
|
|
||||||
use actix_web::{web, HttpResponse};
|
use actix_web::{web, HttpResponse};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
@ -6,34 +5,29 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use super::{IndexParam, UpdateStatusResponse};
|
use super::{IndexParam, UpdateStatusResponse};
|
||||||
use crate::error::ResponseError;
|
use crate::error::ResponseError;
|
||||||
use crate::helpers::Authentication;
|
use crate::extractors::authentication::{policies::*, GuardedData};
|
||||||
use crate::Data;
|
use crate::Data;
|
||||||
|
|
||||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(list_indexes)
|
cfg.service(
|
||||||
.service(get_index)
|
web::resource("indexes")
|
||||||
.service(create_index)
|
.route(web::get().to(list_indexes))
|
||||||
.service(update_index)
|
.route(web::post().to(create_index)),
|
||||||
.service(delete_index)
|
)
|
||||||
.service(get_update_status)
|
.service(
|
||||||
.service(get_all_updates_status);
|
web::resource("/indexes/{index_uid}")
|
||||||
}
|
.route(web::get().to(get_index))
|
||||||
|
.route(web::put().to(update_index))
|
||||||
#[get("/indexes", wrap = "Authentication::Private")]
|
.route(web::delete().to(delete_index)),
|
||||||
async fn list_indexes(data: web::Data<Data>) -> Result<HttpResponse, ResponseError> {
|
)
|
||||||
let indexes = data.list_indexes().await?;
|
.service(
|
||||||
debug!("returns: {:?}", indexes);
|
web::resource("/indexes/{index_uid}/updates")
|
||||||
Ok(HttpResponse::Ok().json(indexes))
|
.route(web::get().to(get_all_updates_status))
|
||||||
}
|
)
|
||||||
|
.service(
|
||||||
#[get("/indexes/{index_uid}", wrap = "Authentication::Private")]
|
web::resource("/indexes/{index_uid}/updates/{update_id}")
|
||||||
async fn get_index(
|
.route(web::get().to(get_update_status))
|
||||||
data: web::Data<Data>,
|
);
|
||||||
path: web::Path<IndexParam>,
|
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
|
||||||
let meta = data.index(path.index_uid.clone()).await?;
|
|
||||||
debug!("returns: {:?}", meta);
|
|
||||||
Ok(HttpResponse::Ok().json(meta))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@ -43,18 +37,6 @@ struct IndexCreateRequest {
|
|||||||
primary_key: Option<String>,
|
primary_key: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/indexes", wrap = "Authentication::Private")]
|
|
||||||
async fn create_index(
|
|
||||||
data: web::Data<Data>,
|
|
||||||
body: web::Json<IndexCreateRequest>,
|
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
|
||||||
debug!("called with params: {:?}", body);
|
|
||||||
let body = body.into_inner();
|
|
||||||
let meta = data.create_index(body.uid, body.primary_key).await?;
|
|
||||||
debug!("returns: {:?}", meta);
|
|
||||||
Ok(HttpResponse::Ok().json(meta))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||||
struct UpdateIndexRequest {
|
struct UpdateIndexRequest {
|
||||||
@ -72,9 +54,32 @@ pub struct UpdateIndexResponse {
|
|||||||
primary_key: Option<String>,
|
primary_key: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[put("/indexes/{index_uid}", wrap = "Authentication::Private")]
|
async fn list_indexes(data: GuardedData<Private, Data>) -> Result<HttpResponse, ResponseError> {
|
||||||
|
let indexes = data.list_indexes().await?;
|
||||||
|
debug!("returns: {:?}", indexes);
|
||||||
|
Ok(HttpResponse::Ok().json(indexes))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_index(
|
||||||
|
data: GuardedData<Private, Data>,
|
||||||
|
body: web::Json<IndexCreateRequest>,
|
||||||
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
|
let body = body.into_inner();
|
||||||
|
let meta = data.create_index(body.uid, body.primary_key).await?;
|
||||||
|
Ok(HttpResponse::Ok().json(meta))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_index(
|
||||||
|
data: GuardedData<Private, Data>,
|
||||||
|
path: web::Path<IndexParam>,
|
||||||
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
|
let meta = data.index(path.index_uid.clone()).await?;
|
||||||
|
debug!("returns: {:?}", meta);
|
||||||
|
Ok(HttpResponse::Ok().json(meta))
|
||||||
|
}
|
||||||
|
|
||||||
async fn update_index(
|
async fn update_index(
|
||||||
data: web::Data<Data>,
|
data: GuardedData<Private, Data>,
|
||||||
path: web::Path<IndexParam>,
|
path: web::Path<IndexParam>,
|
||||||
body: web::Json<UpdateIndexRequest>,
|
body: web::Json<UpdateIndexRequest>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
@ -87,9 +92,8 @@ async fn update_index(
|
|||||||
Ok(HttpResponse::Ok().json(meta))
|
Ok(HttpResponse::Ok().json(meta))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[delete("/indexes/{index_uid}", wrap = "Authentication::Private")]
|
|
||||||
async fn delete_index(
|
async fn delete_index(
|
||||||
data: web::Data<Data>,
|
data: GuardedData<Private, Data>,
|
||||||
path: web::Path<IndexParam>,
|
path: web::Path<IndexParam>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
data.delete_index(path.index_uid.clone()).await?;
|
data.delete_index(path.index_uid.clone()).await?;
|
||||||
@ -102,12 +106,8 @@ struct UpdateParam {
|
|||||||
update_id: u64,
|
update_id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get(
|
|
||||||
"/indexes/{index_uid}/updates/{update_id}",
|
|
||||||
wrap = "Authentication::Private"
|
|
||||||
)]
|
|
||||||
async fn get_update_status(
|
async fn get_update_status(
|
||||||
data: web::Data<Data>,
|
data: GuardedData<Private, Data>,
|
||||||
path: web::Path<UpdateParam>,
|
path: web::Path<UpdateParam>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
let params = path.into_inner();
|
let params = path.into_inner();
|
||||||
@ -119,9 +119,8 @@ async fn get_update_status(
|
|||||||
Ok(HttpResponse::Ok().json(meta))
|
Ok(HttpResponse::Ok().json(meta))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/indexes/{index_uid}/updates", wrap = "Authentication::Private")]
|
|
||||||
async fn get_all_updates_status(
|
async fn get_all_updates_status(
|
||||||
data: web::Data<Data>,
|
data: GuardedData<Private, Data>,
|
||||||
path: web::Path<IndexParam>,
|
path: web::Path<IndexParam>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
let metas = data.get_updates_status(path.into_inner().index_uid).await?;
|
let metas = data.get_updates_status(path.into_inner().index_uid).await?;
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
use actix_web::get;
|
use actix_web::{web, HttpResponse};
|
||||||
use actix_web::web;
|
|
||||||
use actix_web::HttpResponse;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::helpers::Authentication;
|
use crate::extractors::authentication::{policies::*, GuardedData};
|
||||||
use crate::Data;
|
use crate::Data;
|
||||||
|
|
||||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(list);
|
cfg.service(web::resource("/keys").route(web::get().to(list)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
@ -16,8 +14,7 @@ struct KeysResponse {
|
|||||||
public: Option<String>,
|
public: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/keys", wrap = "Authentication::Admin")]
|
async fn list(data: GuardedData<Admin, Data>) -> HttpResponse {
|
||||||
async fn list(data: web::Data<Data>) -> HttpResponse {
|
|
||||||
let api_keys = data.api_keys.clone();
|
let api_keys = data.api_keys.clone();
|
||||||
HttpResponse::Ok().json(&KeysResponse {
|
HttpResponse::Ok().json(&KeysResponse {
|
||||||
private: api_keys.private,
|
private: api_keys.private,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use actix_web::{get, HttpResponse};
|
use actix_web::HttpResponse;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -220,7 +220,6 @@ impl IndexUpdateResponse {
|
|||||||
/// "status": "Meilisearch is running"
|
/// "status": "Meilisearch is running"
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[get("/")]
|
|
||||||
pub async fn running() -> HttpResponse {
|
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" }))
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
use std::collections::{BTreeSet, HashSet};
|
use std::collections::{BTreeSet, HashSet};
|
||||||
|
|
||||||
use actix_web::{get, post, web, HttpResponse};
|
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
use actix_web::{web, HttpResponse};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::error::ResponseError;
|
use crate::error::ResponseError;
|
||||||
use crate::helpers::Authentication;
|
use crate::extractors::authentication::{policies::*, GuardedData};
|
||||||
use crate::index::{default_crop_length, SearchQuery, DEFAULT_SEARCH_LIMIT};
|
use crate::index::{default_crop_length, SearchQuery, DEFAULT_SEARCH_LIMIT};
|
||||||
use crate::routes::IndexParam;
|
use crate::routes::IndexParam;
|
||||||
use crate::Data;
|
use crate::Data;
|
||||||
|
|
||||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(search_with_post).service(search_with_url_query);
|
cfg.service(
|
||||||
|
web::resource("/indexes/{index_uid}/search")
|
||||||
|
.route(web::get().to(search_with_url_query))
|
||||||
|
.route(web::post().to(search_with_post)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
@ -73,9 +77,8 @@ impl From<SearchQueryGet> for SearchQuery {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/indexes/{index_uid}/search", wrap = "Authentication::Public")]
|
|
||||||
async fn search_with_url_query(
|
async fn search_with_url_query(
|
||||||
data: web::Data<Data>,
|
data: GuardedData<Admin, Data>,
|
||||||
path: web::Path<IndexParam>,
|
path: web::Path<IndexParam>,
|
||||||
params: web::Query<SearchQueryGet>,
|
params: web::Query<SearchQueryGet>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
@ -86,9 +89,8 @@ async fn search_with_url_query(
|
|||||||
Ok(HttpResponse::Ok().json(search_result))
|
Ok(HttpResponse::Ok().json(search_result))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/indexes/{index_uid}/search", wrap = "Authentication::Public")]
|
|
||||||
async fn search_with_post(
|
async fn search_with_post(
|
||||||
data: web::Data<Data>,
|
data: GuardedData<Public, Data>,
|
||||||
path: web::Path<IndexParam>,
|
path: web::Path<IndexParam>,
|
||||||
params: web::Json<SearchQuery>,
|
params: web::Json<SearchQuery>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use actix_web::{delete, get, post, web, HttpResponse};
|
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
use actix_web::{web, HttpResponse};
|
||||||
|
|
||||||
use crate::helpers::Authentication;
|
use crate::extractors::authentication::{policies::*, GuardedData};
|
||||||
use crate::index::Settings;
|
use crate::index::Settings;
|
||||||
use crate::Data;
|
use crate::Data;
|
||||||
use crate::{error::ResponseError, index::Unchecked};
|
use crate::{error::ResponseError, index::Unchecked};
|
||||||
@ -11,16 +11,15 @@ macro_rules! make_setting_route {
|
|||||||
($route:literal, $type:ty, $attr:ident, $camelcase_attr:literal) => {
|
($route:literal, $type:ty, $attr:ident, $camelcase_attr:literal) => {
|
||||||
mod $attr {
|
mod $attr {
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use actix_web::{web, HttpResponse};
|
use actix_web::{web, HttpResponse, Resource};
|
||||||
|
|
||||||
use crate::data;
|
use crate::data;
|
||||||
use crate::error::ResponseError;
|
use crate::error::ResponseError;
|
||||||
use crate::helpers::Authentication;
|
|
||||||
use crate::index::Settings;
|
use crate::index::Settings;
|
||||||
|
use crate::extractors::authentication::{GuardedData, policies::*};
|
||||||
|
|
||||||
#[actix_web::delete($route, wrap = "Authentication::Private")]
|
async fn delete(
|
||||||
pub async fn delete(
|
data: GuardedData<Private, data::Data>,
|
||||||
data: web::Data<data::Data>,
|
|
||||||
index_uid: web::Path<String>,
|
index_uid: web::Path<String>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
use crate::index::Settings;
|
use crate::index::Settings;
|
||||||
@ -33,9 +32,8 @@ macro_rules! make_setting_route {
|
|||||||
Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() })))
|
Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() })))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_web::post($route, wrap = "Authentication::Private")]
|
async fn update(
|
||||||
pub async fn update(
|
data: GuardedData<Private, data::Data>,
|
||||||
data: actix_web::web::Data<data::Data>,
|
|
||||||
index_uid: actix_web::web::Path<String>,
|
index_uid: actix_web::web::Path<String>,
|
||||||
body: actix_web::web::Json<Option<$type>>,
|
body: actix_web::web::Json<Option<$type>>,
|
||||||
) -> std::result::Result<HttpResponse, ResponseError> {
|
) -> std::result::Result<HttpResponse, ResponseError> {
|
||||||
@ -49,9 +47,8 @@ macro_rules! make_setting_route {
|
|||||||
Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() })))
|
Ok(HttpResponse::Accepted().json(serde_json::json!({ "updateId": update_status.id() })))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_web::get($route, wrap = "Authentication::Private")]
|
async fn get(
|
||||||
pub async fn get(
|
data: GuardedData<Private, data::Data>,
|
||||||
data: actix_web::web::Data<data::Data>,
|
|
||||||
index_uid: actix_web::web::Path<String>,
|
index_uid: actix_web::web::Path<String>,
|
||||||
) -> std::result::Result<HttpResponse, ResponseError> {
|
) -> std::result::Result<HttpResponse, ResponseError> {
|
||||||
let settings = data.settings(index_uid.into_inner()).await?;
|
let settings = data.settings(index_uid.into_inner()).await?;
|
||||||
@ -60,6 +57,13 @@ macro_rules! make_setting_route {
|
|||||||
let val = json[$camelcase_attr].take();
|
let val = json[$camelcase_attr].take();
|
||||||
Ok(HttpResponse::Ok().json(val))
|
Ok(HttpResponse::Ok().json(val))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn resources() -> Resource {
|
||||||
|
Resource::new($route)
|
||||||
|
.route(web::get().to(get))
|
||||||
|
.route(web::post().to(update))
|
||||||
|
.route(web::delete().to(delete))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -117,14 +121,11 @@ macro_rules! create_services {
|
|||||||
($($mod:ident),*) => {
|
($($mod:ident),*) => {
|
||||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||||
cfg
|
cfg
|
||||||
.service(update_all)
|
.service(web::resource("/indexes/{index_uid}/settings")
|
||||||
.service(get_all)
|
.route(web::post().to(update_all))
|
||||||
.service(delete_all)
|
.route(web::get().to(get_all))
|
||||||
$(
|
.route(web::delete().to(delete_all)))
|
||||||
.service($mod::get)
|
$(.service($mod::resources()))*;
|
||||||
.service($mod::update)
|
|
||||||
.service($mod::delete)
|
|
||||||
)*;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -139,9 +140,8 @@ create_services!(
|
|||||||
ranking_rules
|
ranking_rules
|
||||||
);
|
);
|
||||||
|
|
||||||
#[post("/indexes/{index_uid}/settings", wrap = "Authentication::Private")]
|
|
||||||
async fn update_all(
|
async fn update_all(
|
||||||
data: web::Data<Data>,
|
data: GuardedData<Private, Data>,
|
||||||
index_uid: web::Path<String>,
|
index_uid: web::Path<String>,
|
||||||
body: web::Json<Settings<Unchecked>>,
|
body: web::Json<Settings<Unchecked>>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
@ -154,9 +154,8 @@ async fn update_all(
|
|||||||
Ok(HttpResponse::Accepted().json(json))
|
Ok(HttpResponse::Accepted().json(json))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/indexes/{index_uid}/settings", wrap = "Authentication::Private")]
|
|
||||||
async fn get_all(
|
async fn get_all(
|
||||||
data: web::Data<Data>,
|
data: GuardedData<Private, Data>,
|
||||||
index_uid: web::Path<String>,
|
index_uid: web::Path<String>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
let settings = data.settings(index_uid.into_inner()).await?;
|
let settings = data.settings(index_uid.into_inner()).await?;
|
||||||
@ -164,9 +163,8 @@ async fn get_all(
|
|||||||
Ok(HttpResponse::Ok().json(settings))
|
Ok(HttpResponse::Ok().json(settings))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[delete("/indexes/{index_uid}/settings", wrap = "Authentication::Private")]
|
|
||||||
async fn delete_all(
|
async fn delete_all(
|
||||||
data: web::Data<Data>,
|
data: GuardedData<Private, Data>,
|
||||||
index_uid: web::Path<String>,
|
index_uid: web::Path<String>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
let settings = Settings::cleared();
|
let settings = Settings::cleared();
|
||||||
|
@ -1,23 +1,20 @@
|
|||||||
use actix_web::get;
|
|
||||||
use actix_web::web;
|
|
||||||
use actix_web::HttpResponse;
|
|
||||||
use log::debug;
|
use log::debug;
|
||||||
|
use actix_web::{web, HttpResponse};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::error::ResponseError;
|
use crate::error::ResponseError;
|
||||||
use crate::helpers::Authentication;
|
use crate::extractors::authentication::{policies::*, GuardedData};
|
||||||
use crate::routes::IndexParam;
|
use crate::routes::IndexParam;
|
||||||
use crate::Data;
|
use crate::Data;
|
||||||
|
|
||||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(get_index_stats)
|
cfg.service(web::resource("/indexes/{index_uid}/stats").route(web::get().to(get_index_stats)))
|
||||||
.service(get_stats)
|
.service(web::resource("/stats").route(web::get().to(get_stats)))
|
||||||
.service(get_version);
|
.service(web::resource("/version").route(web::get().to(get_version)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/indexes/{index_uid}/stats", wrap = "Authentication::Private")]
|
|
||||||
async fn get_index_stats(
|
async fn get_index_stats(
|
||||||
data: web::Data<Data>,
|
data: GuardedData<Private, Data>,
|
||||||
path: web::Path<IndexParam>,
|
path: web::Path<IndexParam>,
|
||||||
) -> Result<HttpResponse, ResponseError> {
|
) -> Result<HttpResponse, ResponseError> {
|
||||||
let response = data.get_index_stats(path.index_uid.clone()).await?;
|
let response = data.get_index_stats(path.index_uid.clone()).await?;
|
||||||
@ -26,8 +23,7 @@ async fn get_index_stats(
|
|||||||
Ok(HttpResponse::Ok().json(response))
|
Ok(HttpResponse::Ok().json(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/stats", wrap = "Authentication::Private")]
|
async fn get_stats(data: GuardedData<Private, Data>) -> Result<HttpResponse, ResponseError> {
|
||||||
async fn get_stats(data: web::Data<Data>) -> Result<HttpResponse, ResponseError> {
|
|
||||||
let response = data.get_all_stats().await?;
|
let response = data.get_all_stats().await?;
|
||||||
|
|
||||||
debug!("returns: {:?}", response);
|
debug!("returns: {:?}", response);
|
||||||
@ -42,8 +38,7 @@ struct VersionResponse {
|
|||||||
pkg_version: String,
|
pkg_version: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/version", wrap = "Authentication::Private")]
|
async fn get_version(_data: GuardedData<Private, Data>) -> HttpResponse {
|
||||||
async fn get_version() -> HttpResponse {
|
|
||||||
let commit_sha = match option_env!("COMMIT_SHA") {
|
let commit_sha = match option_env!("COMMIT_SHA") {
|
||||||
Some("") | None => env!("VERGEN_SHA"),
|
Some("") | None => env!("VERGEN_SHA"),
|
||||||
Some(commit_sha) => commit_sha,
|
Some(commit_sha) => commit_sha,
|
||||||
|
@ -14,8 +14,8 @@ async fn delete_one_unexisting_document() {
|
|||||||
let server = Server::new().await;
|
let server = Server::new().await;
|
||||||
let index = server.index("test");
|
let index = server.index("test");
|
||||||
index.create(None).await;
|
index.create(None).await;
|
||||||
let (_response, code) = index.delete_document(0).await;
|
let (response, code) = index.delete_document(0).await;
|
||||||
assert_eq!(code, 202);
|
assert_eq!(code, 202, "{}", response);
|
||||||
let update = index.wait_update_id(0).await;
|
let update = index.wait_update_id(0).await;
|
||||||
assert_eq!(update["status"], "processed");
|
assert_eq!(update["status"], "processed");
|
||||||
}
|
}
|
||||||
@ -85,8 +85,8 @@ async fn clear_all_documents_empty_index() {
|
|||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn delete_batch_unexisting_index() {
|
async fn delete_batch_unexisting_index() {
|
||||||
let server = Server::new().await;
|
let server = Server::new().await;
|
||||||
let (_response, code) = server.index("test").delete_batch(vec![]).await;
|
let (response, code) = server.index("test").delete_batch(vec![]).await;
|
||||||
assert_eq!(code, 404);
|
assert_eq!(code, 404, "{}", response);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use serde_json::{Value, json};
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
use crate::common::Server;
|
use crate::common::Server;
|
||||||
|
|
||||||
@ -11,7 +11,10 @@ static DEFAULT_SETTINGS_VALUES: Lazy<HashMap<&'static str, Value>> = Lazy::new(|
|
|||||||
map.insert("searchable_attributes", json!(["*"]));
|
map.insert("searchable_attributes", json!(["*"]));
|
||||||
map.insert("filterable_attributes", json!([]));
|
map.insert("filterable_attributes", json!([]));
|
||||||
map.insert("distinct_attribute", json!(Value::Null));
|
map.insert("distinct_attribute", json!(Value::Null));
|
||||||
map.insert("ranking_rules", json!(["words", "typo", "proximity", "attribute", "exactness"]));
|
map.insert(
|
||||||
|
"ranking_rules",
|
||||||
|
json!(["words", "typo", "proximity", "attribute", "exactness"]),
|
||||||
|
);
|
||||||
map.insert("stop_words", json!([]));
|
map.insert("stop_words", json!([]));
|
||||||
map.insert("synonyms", json!({}));
|
map.insert("synonyms", json!({}));
|
||||||
map
|
map
|
||||||
|
Loading…
Reference in New Issue
Block a user