Compare commits
10 Commits
f8e2dad58a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
f2a1296454
|
|||
|
d52c4cbe9e
|
|||
|
1282c2b8b5
|
|||
|
f7f4fd2813
|
|||
|
46ce08e02f
|
|||
|
8a049edeeb
|
|||
|
0b7e25c792
|
|||
|
554ae23175
|
|||
|
87aa18b7cc
|
|||
|
86291f1eb5
|
76
Cargo.lock
generated
76
Cargo.lock
generated
@@ -194,6 +194,25 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-login"
|
||||||
|
version = "0.18.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "964ea6eb764a227baa8c3368e45c94d23b6863cc7b880c6c9e341c143c5a5ff7"
|
||||||
|
dependencies = [
|
||||||
|
"axum",
|
||||||
|
"form_urlencoded",
|
||||||
|
"serde",
|
||||||
|
"subtle",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"tower-cookies",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tower-sessions",
|
||||||
|
"tracing",
|
||||||
|
"urlencoding",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-macros"
|
name = "axum-macros"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@@ -743,6 +762,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2391,6 +2411,7 @@ dependencies = [
|
|||||||
name = "libretunes"
|
name = "libretunes"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"axum-login",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"chrono",
|
"chrono",
|
||||||
"config",
|
"config",
|
||||||
@@ -2406,7 +2427,6 @@ dependencies = [
|
|||||||
"rand 0.10.1",
|
"rand 0.10.1",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2435,6 +2455,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4098,6 +4119,22 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-cookies"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "151b5a3e3c45df17466454bb74e9ecedecc955269bdedbf4d150dfa393b55a36"
|
||||||
|
dependencies = [
|
||||||
|
"axum-core",
|
||||||
|
"cookie",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"parking_lot",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-http"
|
name = "tower-http"
|
||||||
version = "0.6.11"
|
version = "0.6.11"
|
||||||
@@ -4137,6 +4174,43 @@ version = "0.3.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-sessions"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43a05911f23e8fae446005fe9b7b97e66d95b6db589dc1c4d59f6a2d4d4927d3"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"http",
|
||||||
|
"time",
|
||||||
|
"tokio",
|
||||||
|
"tower-cookies",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tower-sessions-core",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-sessions-core"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce8cce604865576b7751b7a6bc3058f754569a60d689328bb74c52b1d87e355b"
|
||||||
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"base64",
|
||||||
|
"futures",
|
||||||
|
"http",
|
||||||
|
"parking_lot",
|
||||||
|
"rand 0.8.6",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"time",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.44"
|
version = "0.1.44"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ edition = "2024"
|
|||||||
build = "src/build.rs"
|
build = "src/build.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
axum-login = { version = "0.18.0", optional = true }
|
||||||
cfg-if = "1.0.4"
|
cfg-if = "1.0.4"
|
||||||
chrono = { version = "0.4.45", features = ["serde"] }
|
chrono = { version = "0.4.45", features = ["serde"] }
|
||||||
config = { version = "0.15.24", optional = true }
|
config = { version = "0.15.24", optional = true }
|
||||||
@@ -23,7 +24,6 @@ pbkdf2 = { version = "0.13.0", optional = true, features = ["getrandom", "phc"]
|
|||||||
rand = "0.10.1"
|
rand = "0.10.1"
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
thiserror = "2.0.18"
|
thiserror = "2.0.18"
|
||||||
tokio = { version = "1.52.3", optional = true, features = ["rt-multi-thread"] }
|
|
||||||
tracing = "0.1.44"
|
tracing = "0.1.44"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
@@ -31,6 +31,7 @@ default = ["web"]
|
|||||||
web = ["dioxus/web"]
|
web = ["dioxus/web"]
|
||||||
server = [
|
server = [
|
||||||
"dioxus/server",
|
"dioxus/server",
|
||||||
|
"dep:axum-login",
|
||||||
"dep:config",
|
"dep:config",
|
||||||
"dep:diesel",
|
"dep:diesel",
|
||||||
"dep:diesel-async",
|
"dep:diesel-async",
|
||||||
@@ -38,7 +39,6 @@ server = [
|
|||||||
"dep:dotenvy",
|
"dep:dotenvy",
|
||||||
"dep:fred",
|
"dep:fred",
|
||||||
"dep:pbkdf2",
|
"dep:pbkdf2",
|
||||||
"dep:tokio",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Disabled until supported
|
# Disabled until supported
|
||||||
|
|||||||
@@ -28,9 +28,8 @@ fn main() {
|
|||||||
fn main() -> std::process::ExitCode {
|
fn main() -> std::process::ExitCode {
|
||||||
tracing_setup();
|
tracing_setup();
|
||||||
|
|
||||||
if let Err(e) = server::main() {
|
let Err(e) = server::main();
|
||||||
tracing::error!("Server main failed:\n{e}");
|
tracing::error!("Server main failed:\n{e}");
|
||||||
}
|
|
||||||
|
|
||||||
std::process::ExitCode::FAILURE
|
std::process::ExitCode::FAILURE
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,13 +28,45 @@ use diesel::{
|
|||||||
serialize::ToSql,
|
serialize::ToSql,
|
||||||
sql_types,
|
sql_types,
|
||||||
};
|
};
|
||||||
use pbkdf2::{PasswordHasher, Pbkdf2};
|
use pbkdf2::{
|
||||||
|
PasswordHasher, PasswordVerifier, Pbkdf2, password_hash::Error::PasswordInvalid,
|
||||||
|
phc::PasswordHash,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::util::error::{Error, Result};
|
||||||
|
|
||||||
/// Newtype for a `String`-represented hashed password
|
/// Newtype for a `String`-represented hashed password
|
||||||
#[derive(Clone, Debug, AsExpression, FromSqlRow)]
|
#[derive(Clone, Debug, AsExpression, FromSqlRow)]
|
||||||
#[diesel(sql_type = sql_types::Text)]
|
#[diesel(sql_type = sql_types::Text)]
|
||||||
pub struct HashedPassword(String);
|
pub struct HashedPassword(String);
|
||||||
|
|
||||||
|
impl HashedPassword {
|
||||||
|
/// Check a password attempt against this hashed password
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// `Ok(true)` for a correct password
|
||||||
|
/// `Ok(false)` for an incorrect password
|
||||||
|
/// `Err` for a hashing error
|
||||||
|
pub fn check(&self, password_attempt: String) -> Result<bool> {
|
||||||
|
let pw_hash = PasswordHash::new(&self.0)
|
||||||
|
.map_err(|e| Error::message_here(format!("Error parsing `HashedPassword`: {e}")))?;
|
||||||
|
|
||||||
|
match Pbkdf2::default().verify_password(password_attempt.as_bytes(), &pw_hash) {
|
||||||
|
Ok(()) => Ok(true),
|
||||||
|
Err(PasswordInvalid) => Ok(false),
|
||||||
|
Err(e) => Err(Error::message_here(format!(
|
||||||
|
"Error comparing password attempt against hash: {e}"
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the "session auth hash" for `axum-login`, just the hashed password as bytes
|
||||||
|
pub fn auth_hash(&self) -> &[u8] {
|
||||||
|
self.0.as_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<DB> FromSql<diesel::sql_types::Text, DB> for HashedPassword
|
impl<DB> FromSql<diesel::sql_types::Text, DB> for HashedPassword
|
||||||
where
|
where
|
||||||
DB: diesel::backend::Backend,
|
DB: diesel::backend::Backend,
|
||||||
|
|||||||
91
src/server/auth.rs
Normal file
91
src/server/auth.rs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
use axum_login::{AuthUser, AuthnBackend, UserId};
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
|
||||||
|
use crate::models::user::{DbUser, UserCredentials};
|
||||||
|
use crate::server::database::{DbConn, DbPool};
|
||||||
|
use crate::util::error::{Contextualize, Error, Result};
|
||||||
|
|
||||||
|
impl AuthUser for DbUser {
|
||||||
|
type Id = i32;
|
||||||
|
|
||||||
|
fn id(&self) -> Self::Id {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn session_auth_hash(&self) -> &[u8] {
|
||||||
|
self.hashed_password.auth_hash()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AuthBackend {
|
||||||
|
pub db_pool: DbPool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AuthnBackend for AuthBackend {
|
||||||
|
type User = DbUser;
|
||||||
|
type Credentials = UserCredentials;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
async fn authenticate(
|
||||||
|
&self,
|
||||||
|
attempt_creds: Self::Credentials,
|
||||||
|
) -> Result<Option<Self::User>, Self::Error> {
|
||||||
|
let mut db_conn = self
|
||||||
|
.db_pool
|
||||||
|
.get()
|
||||||
|
.await
|
||||||
|
.err_context("Failed to get database pool connection")?;
|
||||||
|
|
||||||
|
let user = get_user_by_username(&mut db_conn, attempt_creds.username)
|
||||||
|
.await
|
||||||
|
.err_context("Error fetching user for authentication check")?;
|
||||||
|
|
||||||
|
let Some(user) = user else { return Ok(None) };
|
||||||
|
|
||||||
|
let password_result = user
|
||||||
|
.hashed_password
|
||||||
|
.check(attempt_creds.password)
|
||||||
|
.err_context("Error checking user password attempt")?;
|
||||||
|
|
||||||
|
if password_result {
|
||||||
|
Ok(Some(user))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_user(&self, user_id: &UserId<Self>) -> Result<Option<Self::User>, Self::Error> {
|
||||||
|
let mut db_conn = self
|
||||||
|
.db_pool
|
||||||
|
.get()
|
||||||
|
.await
|
||||||
|
.err_context("Failed to get database pool connection")?;
|
||||||
|
|
||||||
|
get_user_by_id(&mut db_conn, *user_id)
|
||||||
|
.await
|
||||||
|
.err_context("Failed fetching user for session")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_user_by_id(db_conn: &mut DbConn, id: i32) -> Result<Option<DbUser>> {
|
||||||
|
crate::schema::users::table
|
||||||
|
.find(id)
|
||||||
|
.first(db_conn)
|
||||||
|
.await
|
||||||
|
.optional()
|
||||||
|
.err_context("Error fetching user from database by id")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_user_by_username(
|
||||||
|
db_conn: &mut DbConn,
|
||||||
|
username: String,
|
||||||
|
) -> Result<Option<DbUser>> {
|
||||||
|
crate::schema::users::table
|
||||||
|
.filter(crate::schema::users::username.eq(username))
|
||||||
|
.first(db_conn)
|
||||||
|
.await
|
||||||
|
.optional()
|
||||||
|
.err_context("Error fetching user from database by username")
|
||||||
|
}
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct AuthConfig {
|
||||||
|
pub open_signup: bool,
|
||||||
|
}
|
||||||
|
|
||||||
/// Build a connection URI from parts
|
/// Build a connection URI from parts
|
||||||
fn format_uri(
|
fn format_uri(
|
||||||
scheme: &str,
|
scheme: &str,
|
||||||
@@ -134,6 +139,7 @@ impl KeyValStoreConfig {
|
|||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
/// Top-level application configuration
|
/// Top-level application configuration
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
pub auth: AuthConfig,
|
||||||
pub database: DatabaseConfig,
|
pub database: DatabaseConfig,
|
||||||
pub key_val_store: KeyValStoreConfig,
|
pub key_val_store: KeyValStoreConfig,
|
||||||
}
|
}
|
||||||
@@ -146,6 +152,7 @@ pub fn load_config() -> Result<Config, config::ConfigError> {
|
|||||||
|
|
||||||
config::Config::builder()
|
config::Config::builder()
|
||||||
.set_default("server.port", 8080)?
|
.set_default("server.port", 8080)?
|
||||||
|
.set_default("auth.open_signup", false)?
|
||||||
.add_source(File::with_name(&format!("/etc/{pkg_name}/config")).required(false))
|
.add_source(File::with_name(&format!("/etc/{pkg_name}/config")).required(false))
|
||||||
.add_source(File::with_name(&format!("/etc/{pkg_name}")).required(false))
|
.add_source(File::with_name(&format!("/etc/{pkg_name}")).required(false))
|
||||||
.add_source(File::with_name("config").required(false))
|
.add_source(File::with_name("config").required(false))
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use crate::util::error::{Contextualize, Error, ErrorType};
|
|||||||
pub const DB_MIGRATIONS: EmbeddedMigrations = embed_migrations!();
|
pub const DB_MIGRATIONS: EmbeddedMigrations = embed_migrations!();
|
||||||
|
|
||||||
pub type DbPool = Pool<AsyncPgConnection>;
|
pub type DbPool = Pool<AsyncPgConnection>;
|
||||||
|
pub type DbConn = AsyncPgConnection;
|
||||||
|
|
||||||
/// Connect to the database using the given URI, and perform migrations
|
/// Connect to the database using the given URI, and perform migrations
|
||||||
pub async fn setup<S: Into<String>>(database_uri: S) -> Result<DbPool, Error> {
|
pub async fn setup<S: Into<String>>(database_uri: S) -> Result<DbPool, Error> {
|
||||||
@@ -26,7 +27,6 @@ pub async fn setup<S: Into<String>>(database_uri: S) -> Result<DbPool, Error> {
|
|||||||
let migration_conn = pool
|
let migration_conn = pool
|
||||||
.get()
|
.get()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ErrorType::Database(e.to_string()))
|
|
||||||
.err_context("Failed to get connection to database")?;
|
.err_context("Failed to get connection to database")?;
|
||||||
|
|
||||||
tracing::debug!("Running migrations...");
|
tracing::debug!("Running migrations...");
|
||||||
|
|||||||
@@ -1,41 +1,33 @@
|
|||||||
use tokio::runtime::Runtime;
|
use dioxus::fullstack::axum::Router;
|
||||||
|
|
||||||
use crate::App;
|
use crate::App;
|
||||||
use crate::server::{config, database, key_val_store};
|
use crate::server::{config, database, key_val_store};
|
||||||
use crate::util::error::{Contextualize, Error, Result};
|
use crate::util::error::{Contextualize, Error, Result};
|
||||||
|
|
||||||
pub fn main() -> Result<()> {
|
pub fn main() -> Result<std::convert::Infallible> {
|
||||||
if let Err(e) = dotenvy::dotenv() {
|
if let Err(e) = dotenvy::dotenv() {
|
||||||
tracing::warn!("Error reading .env: {e}");
|
tracing::warn!("Error reading .env: {e}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `Ok(...?)` is because `dioxus::serve` expects an `anyhow::Result`
|
||||||
|
dioxus::serve(async move || Ok(router_setup().await?));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set up the axum Router
|
||||||
|
async fn router_setup() -> Result<Router> {
|
||||||
tracing::debug!("Loading configuration...");
|
tracing::debug!("Loading configuration...");
|
||||||
let config = config::load_config()
|
let config = config::load_config()
|
||||||
.map_err(|e| Error::message_here(e.to_string()))
|
.map_err(|e| Error::message_here(e.to_string()))
|
||||||
.err_context("Failed to load config")?;
|
.err_context("Failed to load config")?;
|
||||||
|
|
||||||
// `dioxus::launch` creates its own runtime, and starting a runtime inside of a runtime isn't
|
let _db_pool = database::setup(config.database.connection_uri())
|
||||||
// allowed. Therefore, this function can't be made async, and we must manually create a runtime
|
.await
|
||||||
// for any async setup tasks
|
.err_context("Failed database setup")?;
|
||||||
tracing::debug!("Starting setup runtime...");
|
|
||||||
let setup_rt = Runtime::new()
|
|
||||||
.map_err(|e| Error::message_here(e.to_string()))
|
|
||||||
.err_context("Failed to create tokio runtime for server setup")?;
|
|
||||||
|
|
||||||
let (_db_pool, _key_val_pool) = setup_rt.block_on(async {
|
let _key_val_pool = key_val_store::setup(&config.key_val_store.connection_uri())
|
||||||
let db_pool = database::setup(config.database.connection_uri())
|
.await
|
||||||
.await
|
.err_context("Failed key-value store setup")?;
|
||||||
.err_context("Failed database setup")?;
|
|
||||||
|
|
||||||
let key_val_pool = key_val_store::setup(&config.key_val_store.connection_uri())
|
tracing::info!("Setup complete, returning Router...");
|
||||||
.await
|
Ok(dioxus::server::router(App))
|
||||||
.err_context("Failed key-value store setup")?;
|
|
||||||
|
|
||||||
Ok::<_, Error>((db_pool, key_val_pool))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
tracing::info!("Setup complete, launching web server...");
|
|
||||||
dioxus::launch(App);
|
|
||||||
|
|
||||||
Err(Error::message_here("Web server exited"))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
pub mod auth;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod database;
|
pub mod database;
|
||||||
pub mod key_val_store;
|
pub mod key_val_store;
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ impl fmt::Display for ErrorLocation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, thiserror::Error)]
|
#[derive(Debug, Clone, Deserialize, Serialize, thiserror::Error)]
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
@@ -314,6 +314,16 @@ impl From<diesel::result::Error> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This would capture any `deapool::PoolError` and treat it as a database error
|
||||||
|
// but we're only using `deadpool` for our database, so it's fine
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
impl From<diesel_async::pooled_connection::deadpool::PoolError> for Error {
|
||||||
|
#[track_caller]
|
||||||
|
fn from(err: diesel_async::pooled_connection::deadpool::PoolError) -> Self {
|
||||||
|
Error::new_here(ErrorType::Database(format!("{err}")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
impl From<fred::error::Error> for Error {
|
impl From<fred::error::Error> for Error {
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
|
|||||||
Reference in New Issue
Block a user