fix(api): address search review feedback
This commit is contained in:
+4
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "SELECT id AS \"id!: i64\", tmdb_id AS \"tmdb_id!: i64\", title AS \"title!: String\", year, original_language, root_id AS \"root_id!: i64\", wanted AS \"wanted!: bool\", overrides AS \"overrides!: serde_json::Value\", state AS \"state!: String\", blocked AS \"blocked!: bool\", search_attempts AS \"search_attempts!: i64\", last_searched_at FROM movies WHERE tmdb_id = ? ORDER BY id",
|
||||
"query": "SELECT id AS \"id!: i64\", tmdb_id AS \"tmdb_id!: i64\", title AS \"title!: String\", year, original_language, root_id AS \"root_id!: i64\", wanted AS \"wanted!: bool\", overrides AS \"overrides!: serde_json::Value\", state AS \"state!: String\", blocked AS \"blocked!: bool\", search_attempts AS \"search_attempts!: i64\", last_searched_at FROM movies WHERE tmdb_id = ? OR title LIKE ? ESCAPE '\\' ORDER BY title, year, id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -65,10 +65,10 @@
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
@@ -82,5 +82,5 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "2a1c86807011c8dfba5583d7172e8fdfdd2db6e52695e6f4980d1285c9964959"
|
||||
"hash": "8617bd44229736609d860935f22413cc21e26c9cace9a0e2180cca42219cfce3"
|
||||
}
|
||||
+107
-43
@@ -145,7 +145,8 @@ pub async fn search(
|
||||
.trim()
|
||||
.parse::<i64>()
|
||||
.map_err(|_| ApiError::Invalid("invalid TMDB id".into()))?;
|
||||
sqlx::query_as!(Movie, r#"SELECT id AS "id!: i64", tmdb_id AS "tmdb_id!: i64", title AS "title!: String", year, original_language, root_id AS "root_id!: i64", wanted AS "wanted!: bool", overrides AS "overrides!: serde_json::Value", state AS "state!: String", blocked AS "blocked!: bool", search_attempts AS "search_attempts!: i64", last_searched_at FROM movies WHERE tmdb_id = ? ORDER BY id"#, tmdb_id)
|
||||
let pattern = format!("%{}%", escape_like(input));
|
||||
sqlx::query_as!(Movie, r#"SELECT id AS "id!: i64", tmdb_id AS "tmdb_id!: i64", title AS "title!: String", year, original_language, root_id AS "root_id!: i64", wanted AS "wanted!: bool", overrides AS "overrides!: serde_json::Value", state AS "state!: String", blocked AS "blocked!: bool", search_attempts AS "search_attempts!: i64", last_searched_at FROM movies WHERE tmdb_id = ? OR title LIKE ? ESCAPE '\' ORDER BY title, year, id"#, tmdb_id, pattern)
|
||||
.fetch_all(database.pool()).await?
|
||||
} else {
|
||||
let pattern = format!("%{}%", escape_like(input));
|
||||
@@ -154,45 +155,7 @@ pub async fn search(
|
||||
};
|
||||
|
||||
let tmdb = tmdb_client(&state)?;
|
||||
let results = match kind {
|
||||
SearchInputKind::TmdbId => {
|
||||
let id = input
|
||||
.strip_prefix("tmdb:")
|
||||
.unwrap_or(input)
|
||||
.trim()
|
||||
.parse::<u32>()
|
||||
.map_err(|_| ApiError::Invalid("invalid TMDB id".into()))?;
|
||||
let movie = tmdb
|
||||
.movie(id)
|
||||
.await
|
||||
.map_err(|error| upstream_error(&error))?;
|
||||
let year = movie.year();
|
||||
vec![TmdbMovie {
|
||||
tmdb_id: movie.tmdb_id,
|
||||
title: movie.title,
|
||||
original_title: movie.original_title,
|
||||
original_language: movie.original_language,
|
||||
year,
|
||||
overview: movie.overview,
|
||||
poster_path: movie.poster_path,
|
||||
}]
|
||||
}
|
||||
SearchInputKind::ImdbId => tmdb
|
||||
.find_movie_by_imdb(input)
|
||||
.await
|
||||
.map_err(|error| upstream_error(&error))?
|
||||
.into_iter()
|
||||
.map(TmdbMovie::from)
|
||||
.collect(),
|
||||
SearchInputKind::Text => tmdb
|
||||
.search_movies(input, None)
|
||||
.await
|
||||
.map_err(|error| upstream_error(&error))?
|
||||
.into_iter()
|
||||
.map(TmdbMovie::from)
|
||||
.collect(),
|
||||
SearchInputKind::Magnet | SearchInputKind::TorrentUrl => Vec::new(),
|
||||
};
|
||||
let results = search_tmdb(&tmdb, kind, input).await?;
|
||||
if matches!(kind, SearchInputKind::ImdbId) {
|
||||
for result in &results {
|
||||
let tmdb_id = i64::from(result.tmdb_id);
|
||||
@@ -212,6 +175,70 @@ pub async fn search(
|
||||
}))
|
||||
}
|
||||
|
||||
async fn search_tmdb(
|
||||
tmdb: &arr_meta::TmdbClient,
|
||||
kind: SearchInputKind,
|
||||
input: &str,
|
||||
) -> Result<Vec<TmdbMovie>, ApiError> {
|
||||
let results = match kind {
|
||||
SearchInputKind::TmdbId => {
|
||||
let id = input
|
||||
.strip_prefix("tmdb:")
|
||||
.unwrap_or(input)
|
||||
.trim()
|
||||
.parse::<u32>()
|
||||
.map_err(|_| ApiError::Invalid("invalid TMDB id".into()))?;
|
||||
let mut matches = match tmdb.movie(id).await {
|
||||
Ok(movie) => {
|
||||
let year = movie.year();
|
||||
vec![TmdbMovie {
|
||||
tmdb_id: movie.tmdb_id,
|
||||
title: movie.title,
|
||||
original_title: movie.original_title,
|
||||
original_language: movie.original_language,
|
||||
year,
|
||||
overview: movie.overview,
|
||||
poster_path: movie.poster_path,
|
||||
}]
|
||||
}
|
||||
Err(arr_meta::Error::NotFound { .. }) => Vec::new(),
|
||||
Err(error) => return Err(upstream_error(&error)),
|
||||
};
|
||||
if !input.starts_with("tmdb:") {
|
||||
for movie in tmdb
|
||||
.search_movies(input, None)
|
||||
.await
|
||||
.map_err(|error| upstream_error(&error))?
|
||||
.into_iter()
|
||||
.map(TmdbMovie::from)
|
||||
{
|
||||
if !matches
|
||||
.iter()
|
||||
.any(|candidate| candidate.tmdb_id == movie.tmdb_id)
|
||||
{
|
||||
matches.push(movie);
|
||||
}
|
||||
}
|
||||
}
|
||||
matches
|
||||
}
|
||||
SearchInputKind::ImdbId => match tmdb.find_movie_by_imdb(input).await {
|
||||
Ok(movies) => movies.into_iter().map(TmdbMovie::from).collect(),
|
||||
Err(arr_meta::Error::NotFound { .. }) => Vec::new(),
|
||||
Err(error) => return Err(upstream_error(&error)),
|
||||
},
|
||||
SearchInputKind::Text => tmdb
|
||||
.search_movies(input, None)
|
||||
.await
|
||||
.map_err(|error| upstream_error(&error))?
|
||||
.into_iter()
|
||||
.map(TmdbMovie::from)
|
||||
.collect(),
|
||||
SearchInputKind::Magnet | SearchInputKind::TorrentUrl => Vec::new(),
|
||||
};
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get, path = "/api/releases", tag = "search", params(ReleasesQuery),
|
||||
responses(
|
||||
@@ -294,9 +321,14 @@ pub async fn releases(
|
||||
SearchRequest::Text { .. } if indexer.capabilities.search.available => request.clone(),
|
||||
_ => continue,
|
||||
};
|
||||
if let Ok(releases) = prowlarr.search_indexer(indexer.id, &indexer_request).await {
|
||||
for release in releases {
|
||||
classified.push(classify(release, &policy, &overrides, &original_language)?);
|
||||
match prowlarr.search_indexer(indexer.id, &indexer_request).await {
|
||||
Ok(releases) => {
|
||||
for release in releases {
|
||||
classified.push(classify(release, &policy, &overrides, &original_language)?);
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
tracing::warn!(indexer_id = indexer.id, %error, "manual release search failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -630,6 +662,38 @@ mod tests {
|
||||
assert_eq!(response["tmdb"][0]["tmdb_id"], 693_134);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn numeric_titles_survive_a_missing_tmdb_id() {
|
||||
let tmdb = MockServer::start().await;
|
||||
let prowlarr = MockServer::start().await;
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/movie/1917"))
|
||||
.respond_with(ResponseTemplate::new(404))
|
||||
.mount(&tmdb)
|
||||
.await;
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/search/movie"))
|
||||
.and(query_param("query", "1917"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||
"results": [{"id": 530_915, "title": "1917", "original_title": "1917",
|
||||
"original_language": "en", "release_date": "2019-12-25"}]
|
||||
})))
|
||||
.mount(&tmdb)
|
||||
.await;
|
||||
let (_dir, state, base) = application(&tmdb, &prowlarr).await;
|
||||
sqlx::query("INSERT INTO movies (tmdb_id, title, year, original_language, root_id) VALUES (530915, '1917', 2019, 'en', 2)")
|
||||
.execute(state.database().expect("database").pool()).await.expect("movie");
|
||||
|
||||
let response: serde_json::Value = reqwest::get(format!("{base}/api/search?q=1917"))
|
||||
.await
|
||||
.expect("search")
|
||||
.json()
|
||||
.await
|
||||
.expect("json");
|
||||
assert_eq!(response["library"][0]["title"], "1917");
|
||||
assert_eq!(response["tmdb"][0]["title"], "1917");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn manual_releases_are_classified_and_name_rejection_rules() {
|
||||
let tmdb = MockServer::start().await;
|
||||
|
||||
Reference in New Issue
Block a user