96 lines
2.2 KiB
Rust
96 lines
2.2 KiB
Rust
use std::sync::Arc;
|
|
|
|
use async_trait::async_trait;
|
|
use axum::{
|
|
extract::{FromRequest, Query as AxumQuery, RequestParts},
|
|
Extension, Json,
|
|
};
|
|
use semver::Version;
|
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
|
use sqlx::query_as;
|
|
|
|
use crate::{db::PgU32, db_error, Errors, RespResult, State};
|
|
|
|
pub struct Query<T>(T);
|
|
|
|
#[async_trait]
|
|
impl<T, B> FromRequest<B> for Query<T>
|
|
where
|
|
T: DeserializeOwned,
|
|
B: Send,
|
|
{
|
|
type Rejection = Errors;
|
|
|
|
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
|
let res = AxumQuery::<T>::from_request(req).await;
|
|
|
|
match res {
|
|
Ok(AxumQuery(v)) => Ok(Query(v)),
|
|
Err(e) => Err(Errors::new(format_args!("Invalid query options: {e}"))),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct SearchQueryParams {
|
|
q: String,
|
|
per_page: Option<u8>,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
pub struct SearchResponse {
|
|
crates: Vec<Crate>,
|
|
meta: Meta,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
pub struct Crate {
|
|
name: String,
|
|
max_version: Version,
|
|
description: String,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
pub struct Meta {
|
|
total: u32,
|
|
}
|
|
|
|
pub async fn search(
|
|
Query(params): Query<SearchQueryParams>,
|
|
Extension(state): Extension<Arc<State>>,
|
|
) -> RespResult<Json<SearchResponse>> {
|
|
let crates = query_as::<_, (PgU32, String)>(
|
|
"SELECT id, name FROM crates ORDER BY SIMILARITY(name, $1) DESC LIMIT $2;",
|
|
)
|
|
.bind(¶ms.q)
|
|
.bind(i16::from(params.per_page.unwrap_or(10)))
|
|
.fetch_all(&state.db)
|
|
.await
|
|
.map_err(db_error)?;
|
|
|
|
let mut crates2 = Vec::new();
|
|
for craet in crates {
|
|
let v: Option<(String, Option<String>)> = query_as(
|
|
"SELECT vers, description FROM versions WHERE crate_id = $1 ORDER BY vers DESC LIMIT 1",
|
|
)
|
|
.bind(craet.0)
|
|
.fetch_optional(&state.db)
|
|
.await
|
|
.map_err(db_error)?;
|
|
|
|
if let Some((vers, desc)) = v {
|
|
crates2.push(Crate {
|
|
name: craet.1,
|
|
max_version: vers.parse().unwrap(),
|
|
description: desc.unwrap_or_else(|| "".to_owned()),
|
|
})
|
|
}
|
|
}
|
|
|
|
Ok(Json(SearchResponse {
|
|
crates: crates2,
|
|
// TODO: total
|
|
meta: Meta { total: 0 },
|
|
}))
|
|
}
|