You've already forked AstralRinth
forked from didirus/AstralRinth
Improve error handling (#33)
* refactor: improve error handling * fix: specify bind address instead of port * fix: remove temporary testing file * fix(errors): change error names to snake_case * refactor(errors): split indexing error types, remove unused errors * feat: add env variable checking at program start This just checks whether the enviroment variables exist and can parse to the given type and gives a warning if they can't. This should prevent cases where the program fails at runtime due to checking an environment variable that doesn't exist.
This commit is contained in:
2
.env
2
.env
@@ -4,7 +4,7 @@ DEBUG=true
|
|||||||
MONGODB_ADDR=mongodb://localhost:27017
|
MONGODB_ADDR=mongodb://localhost:27017
|
||||||
MEILISEARCH_ADDR=http://localhost:7700
|
MEILISEARCH_ADDR=http://localhost:7700
|
||||||
|
|
||||||
PORT=8000
|
BIND_ADDR=127.0.0.1:8000
|
||||||
|
|
||||||
BACKBLAZE_KEY_ID=none
|
BACKBLAZE_KEY_ID=none
|
||||||
BACKBLAZE_KEY=none
|
BACKBLAZE_KEY=none
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ use mongodb::Client;
|
|||||||
pub async fn connect() -> Result<Client, Error> {
|
pub async fn connect() -> Result<Client, Error> {
|
||||||
info!("Initializing database connection");
|
info!("Initializing database connection");
|
||||||
|
|
||||||
let mut client_options = ClientOptions::parse(&dotenv::var("MONGODB_ADDR").unwrap()).await?;
|
let mongodb_addr = dotenv::var("MONGODB_ADDR").expect("`MONGO_ADDR` not in .env");
|
||||||
|
let mut client_options = ClientOptions::parse(&mongodb_addr).await?;
|
||||||
client_options.app_name = Some("labrinth".to_string());
|
client_options.app_name = Some("labrinth".to_string());
|
||||||
|
|
||||||
Client::with_options(client_options)
|
Client::with_options(client_options)
|
||||||
|
|||||||
@@ -18,10 +18,8 @@ pub use delete::DeleteFileData;
|
|||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum FileHostingError {
|
pub enum FileHostingError {
|
||||||
#[error("Error while accessing the data from remote")]
|
#[error("Error while accessing the data from backblaze")]
|
||||||
RemoteWebsiteError(#[from] reqwest::Error),
|
BackblazeError(#[from] reqwest::Error),
|
||||||
#[error("Error while serializing or deserializing JSON")]
|
|
||||||
SerDeError(#[from] serde_json::Error),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
39
src/main.rs
39
src/main.rs
@@ -17,7 +17,11 @@ async fn main() -> std::io::Result<()> {
|
|||||||
env_logger::from_env(Env::default().default_filter_or("info")).init();
|
env_logger::from_env(Env::default().default_filter_or("info")).init();
|
||||||
dotenv::dotenv().ok();
|
dotenv::dotenv().ok();
|
||||||
|
|
||||||
let client = database::connect().await.unwrap();
|
check_env_vars();
|
||||||
|
|
||||||
|
let client = database::connect()
|
||||||
|
.await
|
||||||
|
.expect("Database connection failed");
|
||||||
|
|
||||||
// Get executable path
|
// Get executable path
|
||||||
let mut exe_path = env::current_exe()?.parent().unwrap().to_path_buf();
|
let mut exe_path = env::current_exe()?.parent().unwrap().to_path_buf();
|
||||||
@@ -28,11 +32,11 @@ async fn main() -> std::io::Result<()> {
|
|||||||
if env::args().any(|x| x == "regen") {
|
if env::args().any(|x| x == "regen") {
|
||||||
// User forced regen of indexing
|
// User forced regen of indexing
|
||||||
info!("Forced regeneration of indexes!");
|
info!("Forced regeneration of indexes!");
|
||||||
index_mods(client).await.unwrap();
|
index_mods(client).await.expect("Mod indexing failed");
|
||||||
} else if exe_path.exists() {
|
} else if !exe_path.exists() {
|
||||||
// The indexes were not created, or the version was upgraded
|
// The indexes were not created, or the version was upgraded
|
||||||
info!("Indexing of mods for first time...");
|
info!("Indexing of mods for first time...");
|
||||||
index_mods(client).await.unwrap();
|
index_mods(client).await.expect("Mod indexing failed");
|
||||||
// Create the lock file
|
// Create the lock file
|
||||||
File::create(exe_path)?;
|
File::create(exe_path)?;
|
||||||
}
|
}
|
||||||
@@ -48,7 +52,32 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.service(routes::mod_search)
|
.service(routes::mod_search)
|
||||||
.default_service(web::get().to(routes::not_found))
|
.default_service(web::get().to(routes::not_found))
|
||||||
})
|
})
|
||||||
.bind("127.0.0.1:".to_string() + &dotenv::var("PORT").unwrap())?
|
.bind(dotenv::var("BIND_ADDR").unwrap())?
|
||||||
.run()
|
.run()
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is so that env vars not used immediately don't panic at runtime
|
||||||
|
fn check_env_vars() {
|
||||||
|
fn check_var<T: std::str::FromStr>(var: &str) {
|
||||||
|
if dotenv::var(var)
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| s.parse::<T>().ok())
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
log::warn!(
|
||||||
|
"Variable `{}` missing in dotenv or not of type `{}`",
|
||||||
|
var,
|
||||||
|
std::any::type_name::<T>()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
check_var::<bool>("INDEX_CURSEFORGE");
|
||||||
|
check_var::<String>("MONGODB_ADDR");
|
||||||
|
check_var::<String>("MEILISEARCH_ADDR");
|
||||||
|
check_var::<String>("BIND_ADDR");
|
||||||
|
|
||||||
|
check_var::<String>("BACKBLAZE_KEY_ID");
|
||||||
|
check_var::<String>("BACKBLAZE_KEY");
|
||||||
|
check_var::<String>("BACKBLAZE_BUCKET_ID");
|
||||||
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ pub struct Base62Id(pub u64);
|
|||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum DecodingError {
|
pub enum DecodingError {
|
||||||
/// Encountered a non base62 character in base62 string
|
/// Encountered a non base62 character in base62 string
|
||||||
#[error("Invalid character `{0:?}` in base62 encoding")]
|
#[error("Invalid character {0:?} in base62 encoding")]
|
||||||
InvalidBase62(char),
|
InvalidBase62(char),
|
||||||
/// Encountered integer overflow when decoding a base62 id.
|
/// Encountered integer overflow when decoding a base62 id.
|
||||||
#[error("Base62 decoding overflowed")]
|
#[error("Base62 decoding overflowed")]
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
use crate::models::mods::SearchRequest;
|
use crate::models::mods::SearchRequest;
|
||||||
use crate::search::search_for_mod;
|
use crate::search::{search_for_mod, SearchError};
|
||||||
use actix_web::{get, web, HttpResponse};
|
use actix_web::{get, web, HttpResponse};
|
||||||
|
|
||||||
#[get("api/v1/mods")]
|
#[get("api/v1/mods")]
|
||||||
pub fn mod_search(web::Query(info): web::Query<SearchRequest>) -> HttpResponse {
|
pub async fn mod_search(
|
||||||
//TODO: Fix this line with anyhow
|
web::Query(info): web::Query<SearchRequest>,
|
||||||
let body = serde_json::to_string(&search_for_mod(&info).unwrap()).unwrap();
|
) -> Result<HttpResponse, SearchError> {
|
||||||
|
Ok(HttpResponse::Ok().json(search_for_mod(&info)?))
|
||||||
HttpResponse::Ok()
|
|
||||||
.content_type("application/json")
|
|
||||||
.body(body)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::search::{SearchError, SearchMod};
|
use super::IndexingError;
|
||||||
|
use crate::search::SearchMod;
|
||||||
use log::info;
|
use log::info;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -47,7 +48,7 @@ pub struct CurseForgeMod {
|
|||||||
pub async fn index_curseforge(
|
pub async fn index_curseforge(
|
||||||
start_index: i32,
|
start_index: i32,
|
||||||
end_index: i32,
|
end_index: i32,
|
||||||
) -> Result<Vec<SearchMod>, SearchError> {
|
) -> Result<Vec<SearchMod>, IndexingError> {
|
||||||
info!("Indexing curseforge mods!");
|
info!("Indexing curseforge mods!");
|
||||||
|
|
||||||
let mut docs_to_add: Vec<SearchMod> = vec![];
|
let mut docs_to_add: Vec<SearchMod> = vec![];
|
||||||
@@ -60,10 +61,13 @@ pub async fn index_curseforge(
|
|||||||
(start_index..end_index).collect::<Vec<_>>()
|
(start_index..end_index).collect::<Vec<_>>()
|
||||||
))
|
))
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await
|
||||||
|
.map_err(IndexingError::CurseforgeImportError)?;
|
||||||
|
|
||||||
let text = &res.text().await?;
|
let curseforge_mods: Vec<CurseForgeMod> = res
|
||||||
let curseforge_mods: Vec<CurseForgeMod> = serde_json::from_str(text)?;
|
.json()
|
||||||
|
.await
|
||||||
|
.map_err(IndexingError::CurseforgeImportError)?;
|
||||||
|
|
||||||
for curseforge_mod in curseforge_mods {
|
for curseforge_mod in curseforge_mods {
|
||||||
if curseforge_mod.game_slug != "minecraft"
|
if curseforge_mod.game_slug != "minecraft"
|
||||||
@@ -78,15 +82,14 @@ pub async fn index_curseforge(
|
|||||||
let mut using_fabric = false;
|
let mut using_fabric = false;
|
||||||
|
|
||||||
for version in curseforge_mod.game_version_latest_files {
|
for version in curseforge_mod.game_version_latest_files {
|
||||||
let version_number: String = version
|
if let Some(parsed) = version
|
||||||
.game_version
|
.game_version
|
||||||
.chars()
|
.get(2..)
|
||||||
.skip(2)
|
.and_then(|f| f.parse::<f32>().ok())
|
||||||
.take(version.game_version.len())
|
{
|
||||||
.collect();
|
if parsed < 14.0 {
|
||||||
|
using_forge = true;
|
||||||
if version_number.parse::<f32>()? < 14.0 {
|
}
|
||||||
using_forge = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mod_game_versions.push(version.game_version);
|
mod_game_versions.push(version.game_version);
|
||||||
@@ -188,17 +191,13 @@ pub async fn index_curseforge(
|
|||||||
date_created: curseforge_mod.date_created.chars().take(10).collect(),
|
date_created: curseforge_mod.date_created.chars().take(10).collect(),
|
||||||
created: curseforge_mod
|
created: curseforge_mod
|
||||||
.date_created
|
.date_created
|
||||||
.chars()
|
.parse::<chrono::DateTime<chrono::Utc>>()?
|
||||||
.filter(|c| c.is_ascii_digit())
|
.timestamp(),
|
||||||
.collect::<String>()
|
|
||||||
.parse()?,
|
|
||||||
date_modified: curseforge_mod.date_modified.chars().take(10).collect(),
|
date_modified: curseforge_mod.date_modified.chars().take(10).collect(),
|
||||||
updated: curseforge_mod
|
updated: curseforge_mod
|
||||||
.date_modified
|
.date_modified
|
||||||
.chars()
|
.parse::<chrono::DateTime<chrono::Utc>>()?
|
||||||
.filter(|c| c.is_ascii_digit())
|
.timestamp(),
|
||||||
.collect::<String>()
|
|
||||||
.parse()?,
|
|
||||||
latest_version,
|
latest_version,
|
||||||
empty: String::from("{}{}{}"),
|
empty: String::from("{}{}{}"),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ use futures::StreamExt;
|
|||||||
use log::info;
|
use log::info;
|
||||||
|
|
||||||
use crate::database::models::Item;
|
use crate::database::models::Item;
|
||||||
use crate::database::{Mod, Version};
|
use crate::database::{DatabaseError, Mod, Version};
|
||||||
|
|
||||||
use crate::search::{SearchError, SearchMod};
|
use super::IndexingError;
|
||||||
|
use crate::search::SearchMod;
|
||||||
|
|
||||||
pub async fn index_local(client: mongodb::Client) -> Result<Vec<SearchMod>, SearchError> {
|
pub async fn index_local(client: mongodb::Client) -> Result<Vec<SearchMod>, IndexingError> {
|
||||||
info!("Indexing local mods!");
|
info!("Indexing local mods!");
|
||||||
|
|
||||||
let mut docs_to_add: Vec<SearchMod> = vec![];
|
let mut docs_to_add: Vec<SearchMod> = vec![];
|
||||||
@@ -17,17 +18,26 @@ pub async fn index_local(client: mongodb::Client) -> Result<Vec<SearchMod>, Sear
|
|||||||
let mods = db.collection("mods");
|
let mods = db.collection("mods");
|
||||||
let versions = db.collection("versions");
|
let versions = db.collection("versions");
|
||||||
|
|
||||||
let mut results = mods.find(None, None).await?;
|
let mut results = mods
|
||||||
|
.find(None, None)
|
||||||
|
.await
|
||||||
|
.map_err(DatabaseError::LocalDatabaseError)?;
|
||||||
|
|
||||||
while let Some(unparsed_result) = results.next().await {
|
while let Some(unparsed_result) = results.next().await {
|
||||||
let result: Mod = *Mod::from_doc(unparsed_result?)?;
|
let result: Mod =
|
||||||
|
*Mod::from_doc(unparsed_result.map_err(DatabaseError::LocalDatabaseError)?)?;
|
||||||
|
|
||||||
let mut mod_versions = versions.find(doc! { "mod_id": result.id}, None).await?;
|
let mut mod_versions = versions
|
||||||
|
.find(doc! { "mod_id": result.id }, None)
|
||||||
|
.await
|
||||||
|
.map_err(DatabaseError::LocalDatabaseError)?;
|
||||||
|
|
||||||
let mut mod_game_versions = vec![];
|
let mut mod_game_versions = vec![];
|
||||||
|
|
||||||
while let Some(unparsed_version) = mod_versions.next().await {
|
while let Some(unparsed_version) = mod_versions.next().await {
|
||||||
let mut version: Version = *Version::from_doc(unparsed_version?)?;
|
let mut version = unparsed_version
|
||||||
|
.map_err(DatabaseError::LocalDatabaseError)
|
||||||
|
.and_then(Version::from_doc)?;
|
||||||
mod_game_versions.append(&mut version.game_versions);
|
mod_game_versions.append(&mut version.game_versions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,29 @@ pub mod local_import;
|
|||||||
|
|
||||||
use crate::search::indexing::curseforge_import::index_curseforge;
|
use crate::search::indexing::curseforge_import::index_curseforge;
|
||||||
use crate::search::indexing::local_import::index_local;
|
use crate::search::indexing::local_import::index_local;
|
||||||
use crate::search::{SearchError, SearchMod};
|
use crate::search::SearchMod;
|
||||||
use meilisearch_sdk::client::Client;
|
use meilisearch_sdk::client::Client;
|
||||||
use meilisearch_sdk::settings::Settings;
|
use meilisearch_sdk::settings::Settings;
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
pub async fn index_mods(db: mongodb::Client) -> Result<(), SearchError> {
|
#[derive(Error, Debug)]
|
||||||
|
pub enum IndexingError {
|
||||||
|
#[error("Error while connecting to the MeiliSearch database")]
|
||||||
|
IndexDBError(meilisearch_sdk::errors::Error),
|
||||||
|
#[error("Error while importing mods from CurseForge")]
|
||||||
|
CurseforgeImportError(reqwest::Error),
|
||||||
|
#[error("Error while serializing or deserializing JSON: {0}")]
|
||||||
|
SerDeError(#[from] serde_json::Error),
|
||||||
|
#[error("Error while parsing a timestamp: {0}")]
|
||||||
|
ParseDateError(#[from] chrono::format::ParseError),
|
||||||
|
#[error("Database Error: {0}")]
|
||||||
|
DatabaseError(#[from] crate::database::DatabaseError),
|
||||||
|
#[error("Environment Error")]
|
||||||
|
EnvError(#[from] dotenv::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn index_mods(db: mongodb::Client) -> Result<(), IndexingError> {
|
||||||
// Check if the index exists
|
// Check if the index exists
|
||||||
let address = &*dotenv::var("MEILISEARCH_ADDR")?;
|
let address = &*dotenv::var("MEILISEARCH_ADDR")?;
|
||||||
let client = Client::new(address, "");
|
let client = Client::new(address, "");
|
||||||
@@ -17,66 +34,73 @@ pub async fn index_mods(db: mongodb::Client) -> Result<(), SearchError> {
|
|||||||
let mut docs_to_add: Vec<SearchMod> = vec![];
|
let mut docs_to_add: Vec<SearchMod> = vec![];
|
||||||
|
|
||||||
docs_to_add.append(&mut index_local(db.clone()).await?);
|
docs_to_add.append(&mut index_local(db.clone()).await?);
|
||||||
if dotenv::var("INDEX_CURSEFORGE")
|
if dotenv::var("INDEX_CURSEFORGE")?
|
||||||
.expect("`INDEX_CURSEFORGE` is missing in the .env file.")
|
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap()
|
.expect("`INDEX_CURSEFORGE` is not a boolean.")
|
||||||
{
|
{
|
||||||
docs_to_add.append(&mut index_curseforge(1, 400000).await?);
|
docs_to_add.append(&mut index_curseforge(1, 400000).await?);
|
||||||
}
|
}
|
||||||
//Write Indexes
|
//Write Indexes
|
||||||
//Relevance Index
|
//Relevance Index
|
||||||
|
|
||||||
let mut relevance_index = client.get_or_create("relevance_mods").unwrap();
|
let mut relevance_index = client
|
||||||
|
.get_or_create("relevance_mods")
|
||||||
|
.map_err(IndexingError::IndexDBError)?;
|
||||||
|
|
||||||
let mut relevance_rules = default_rules();
|
let mut relevance_rules = default_rules();
|
||||||
relevance_rules.push_back("desc(downloads)".to_string());
|
relevance_rules.push_back("desc(downloads)".to_string());
|
||||||
|
|
||||||
relevance_index
|
relevance_index
|
||||||
.set_settings(&default_settings().with_ranking_rules(relevance_rules.into()))
|
.set_settings(&default_settings().with_ranking_rules(relevance_rules.into()))
|
||||||
.unwrap();
|
.map_err(IndexingError::IndexDBError)?;
|
||||||
relevance_index
|
relevance_index
|
||||||
.add_documents(docs_to_add.clone(), Some("mod_id"))
|
.add_documents(docs_to_add.clone(), Some("mod_id"))
|
||||||
.unwrap();
|
.map_err(IndexingError::IndexDBError)?;
|
||||||
|
|
||||||
//Downloads Index
|
//Downloads Index
|
||||||
let mut downloads_index = client.get_or_create("downloads_mods").unwrap();
|
let mut downloads_index = client
|
||||||
|
.get_or_create("downloads_mods")
|
||||||
|
.map_err(IndexingError::IndexDBError)?;
|
||||||
|
|
||||||
let mut downloads_rules = default_rules();
|
let mut downloads_rules = default_rules();
|
||||||
downloads_rules.push_front("desc(downloads)".to_string());
|
downloads_rules.push_front("desc(downloads)".to_string());
|
||||||
|
|
||||||
downloads_index
|
downloads_index
|
||||||
.set_settings(&default_settings().with_ranking_rules(downloads_rules.into()))
|
.set_settings(&default_settings().with_ranking_rules(downloads_rules.into()))
|
||||||
.unwrap();
|
.map_err(IndexingError::IndexDBError)?;
|
||||||
downloads_index
|
downloads_index
|
||||||
.add_documents(docs_to_add.clone(), Some("mod_id"))
|
.add_documents(docs_to_add.clone(), Some("mod_id"))
|
||||||
.unwrap();
|
.map_err(IndexingError::IndexDBError)?;
|
||||||
|
|
||||||
//Updated Index
|
//Updated Index
|
||||||
let mut updated_index = client.get_or_create("updated_mods").unwrap();
|
let mut updated_index = client
|
||||||
|
.get_or_create("updated_mods")
|
||||||
|
.map_err(IndexingError::IndexDBError)?;
|
||||||
|
|
||||||
let mut updated_rules = default_rules();
|
let mut updated_rules = default_rules();
|
||||||
updated_rules.push_front("desc(updated)".to_string());
|
updated_rules.push_front("desc(updated)".to_string());
|
||||||
|
|
||||||
updated_index
|
updated_index
|
||||||
.set_settings(&default_settings().with_ranking_rules(updated_rules.into()))
|
.set_settings(&default_settings().with_ranking_rules(updated_rules.into()))
|
||||||
.unwrap();
|
.map_err(IndexingError::IndexDBError)?;
|
||||||
updated_index
|
updated_index
|
||||||
.add_documents(docs_to_add.clone(), Some("mod_id"))
|
.add_documents(docs_to_add.clone(), Some("mod_id"))
|
||||||
.unwrap();
|
.map_err(IndexingError::IndexDBError)?;
|
||||||
|
|
||||||
//Created Index
|
//Created Index
|
||||||
let mut newest_index = client.get_or_create("newest_mods").unwrap();
|
let mut newest_index = client
|
||||||
|
.get_or_create("newest_mods")
|
||||||
|
.map_err(IndexingError::IndexDBError)?;
|
||||||
|
|
||||||
let mut newest_rules = default_rules();
|
let mut newest_rules = default_rules();
|
||||||
newest_rules.push_back("desc(created)".to_string());
|
newest_rules.push_back("desc(created)".to_string());
|
||||||
|
|
||||||
newest_index
|
newest_index
|
||||||
.set_settings(&default_settings().with_ranking_rules(newest_rules.into()))
|
.set_settings(&default_settings().with_ranking_rules(newest_rules.into()))
|
||||||
.unwrap();
|
.map_err(IndexingError::IndexDBError)?;
|
||||||
newest_index
|
newest_index
|
||||||
.add_documents(docs_to_add.clone(), Some("mod_id"))
|
.add_documents(docs_to_add.clone(), Some("mod_id"))
|
||||||
.unwrap();
|
.map_err(IndexingError::IndexDBError)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -124,8 +148,8 @@ fn default_settings() -> Settings {
|
|||||||
];
|
];
|
||||||
|
|
||||||
Settings::new()
|
Settings::new()
|
||||||
.with_displayed_attributes(displayed_attributes.clone())
|
.with_displayed_attributes(displayed_attributes)
|
||||||
.with_searchable_attributes(searchable_attributes.clone())
|
.with_searchable_attributes(searchable_attributes)
|
||||||
.with_accept_new_fields(true)
|
.with_accept_new_fields(true)
|
||||||
.with_stop_words(vec![])
|
.with_stop_words(vec![])
|
||||||
.with_synonyms(HashMap::new())
|
.with_synonyms(HashMap::new())
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use crate::database::DatabaseError;
|
use crate::models::error::ApiError;
|
||||||
use crate::models::mods::SearchRequest;
|
use crate::models::mods::SearchRequest;
|
||||||
|
use actix_web::http::StatusCode;
|
||||||
|
use actix_web::web::HttpResponse;
|
||||||
use meilisearch_sdk::client::Client;
|
use meilisearch_sdk::client::Client;
|
||||||
use meilisearch_sdk::document::Document;
|
use meilisearch_sdk::document::Document;
|
||||||
use meilisearch_sdk::search::Query;
|
use meilisearch_sdk::search::Query;
|
||||||
@@ -10,24 +12,39 @@ pub mod indexing;
|
|||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum SearchError {
|
pub enum SearchError {
|
||||||
#[error("Error while connection to the MeiliSearch database")]
|
#[error("Error while connecting to the MeiliSearch database")]
|
||||||
IndexDBError(),
|
IndexDBError(meilisearch_sdk::errors::Error),
|
||||||
#[error("Error while connecting to the local server")]
|
#[error("Error while serializing or deserializing JSON: {0}")]
|
||||||
LocalDatabaseError(#[from] mongodb::error::Error),
|
|
||||||
#[error("Error while accessing the data from remote")]
|
|
||||||
RemoteWebsiteError(#[from] reqwest::Error),
|
|
||||||
#[error("Error while serializing or deserializing JSON")]
|
|
||||||
SerDeError(#[from] serde_json::Error),
|
SerDeError(#[from] serde_json::Error),
|
||||||
#[error("Error while parsing float")]
|
#[error("Error while parsing an integer: {0}")]
|
||||||
FloatParsingError(#[from] std::num::ParseFloatError),
|
|
||||||
#[error("Error while parsing float")]
|
|
||||||
IntParsingError(#[from] std::num::ParseIntError),
|
IntParsingError(#[from] std::num::ParseIntError),
|
||||||
#[error("Error while parsing BSON")]
|
|
||||||
DatabaseError(#[from] DatabaseError),
|
|
||||||
#[error("Environment Error")]
|
#[error("Environment Error")]
|
||||||
EnvError(#[from] dotenv::Error),
|
EnvError(#[from] dotenv::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl actix_web::ResponseError for SearchError {
|
||||||
|
fn status_code(&self) -> StatusCode {
|
||||||
|
match self {
|
||||||
|
SearchError::EnvError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
SearchError::IndexDBError(..) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
SearchError::SerDeError(..) => StatusCode::BAD_REQUEST,
|
||||||
|
SearchError::IntParsingError(..) => StatusCode::BAD_REQUEST,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_response(&self) -> HttpResponse {
|
||||||
|
HttpResponse::build(self.status_code()).json(ApiError {
|
||||||
|
error: match self {
|
||||||
|
SearchError::EnvError(..) => "environment_error",
|
||||||
|
SearchError::IndexDBError(..) => "indexdb_error",
|
||||||
|
SearchError::SerDeError(..) => "invalid_input",
|
||||||
|
SearchError::IntParsingError(..) => "invalid_input",
|
||||||
|
},
|
||||||
|
description: &self.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct SearchMod {
|
pub struct SearchMod {
|
||||||
pub mod_id: i32,
|
pub mod_id: i32,
|
||||||
@@ -57,38 +74,24 @@ impl Document for SearchMod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn search_for_mod(info: &SearchRequest) -> Result<Vec<SearchMod>, SearchError> {
|
pub fn search_for_mod(info: &SearchRequest) -> Result<Vec<SearchMod>, SearchError> {
|
||||||
|
use std::borrow::Cow;
|
||||||
let address = &*dotenv::var("MEILISEARCH_ADDR")?;
|
let address = &*dotenv::var("MEILISEARCH_ADDR")?;
|
||||||
let client = Client::new(address, "");
|
let client = Client::new(address, "");
|
||||||
|
|
||||||
let search_query: &str;
|
let filters: Cow<_> = match (info.filters.as_deref(), info.version.as_deref()) {
|
||||||
let mut filters = String::new();
|
(Some(f), Some(v)) => format!("({}) AND ({})", f, v).into(),
|
||||||
let mut offset = 0;
|
(Some(f), None) => f.into(),
|
||||||
let mut index = "relevance";
|
(None, Some(v)) => v.into(),
|
||||||
|
(None, None) => "".into(),
|
||||||
|
};
|
||||||
|
|
||||||
match info.query.as_ref() {
|
let offset = info.offset.as_deref().unwrap_or("0").parse()?;
|
||||||
Some(q) => search_query = q,
|
let index = info.index.as_deref().unwrap_or("relevance");
|
||||||
None => search_query = "{}{}{}",
|
let search_query: &str = info
|
||||||
}
|
.query
|
||||||
|
.as_deref()
|
||||||
if let Some(f) = info.filters.as_ref() {
|
.filter(|s| !s.is_empty())
|
||||||
filters = f.clone();
|
.unwrap_or("{}{}{}");
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(v) = info.version.as_ref() {
|
|
||||||
if filters.is_empty() {
|
|
||||||
filters = v.clone();
|
|
||||||
} else {
|
|
||||||
filters = format!("({}) AND ({})", filters, v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(o) = info.offset.as_ref() {
|
|
||||||
offset = o.parse().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(s) = info.index.as_ref() {
|
|
||||||
index = s;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut query = Query::new(search_query).with_limit(10).with_offset(offset);
|
let mut query = Query::new(search_query).with_limit(10).with_offset(offset);
|
||||||
|
|
||||||
@@ -98,8 +101,8 @@ pub fn search_for_mod(info: &SearchRequest) -> Result<Vec<SearchMod>, SearchErro
|
|||||||
|
|
||||||
Ok(client
|
Ok(client
|
||||||
.get_index(format!("{}_mods", index).as_ref())
|
.get_index(format!("{}_mods", index).as_ref())
|
||||||
.unwrap()
|
.map_err(SearchError::IndexDBError)?
|
||||||
.search::<SearchMod>(&query)
|
.search::<SearchMod>(&query)
|
||||||
.unwrap()
|
.map_err(SearchError::IndexDBError)?
|
||||||
.hits)
|
.hits)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user