2024-09-19 08:03:58 +00:00
|
|
|
|
use crate::{newtypes::DbUrl, CommentSortType, PostSortType};
|
2024-03-18 09:36:49 +00:00
|
|
|
|
use chrono::{DateTime, TimeDelta, Utc};
|
2023-03-03 09:30:03 +00:00
|
|
|
|
use deadpool::Runtime;
|
2022-05-03 17:44:13 +00:00
|
|
|
|
use diesel::{
|
2023-08-24 15:27:00 +00:00
|
|
|
|
helper_types::AsExprOf,
|
2022-09-26 14:09:32 +00:00
|
|
|
|
pg::Pg,
|
2024-01-24 15:22:33 +00:00
|
|
|
|
query_builder::{Query, QueryFragment},
|
|
|
|
|
query_dsl::methods::LimitDsl,
|
2024-04-16 12:48:15 +00:00
|
|
|
|
result::{
|
|
|
|
|
ConnectionError,
|
|
|
|
|
ConnectionResult,
|
|
|
|
|
Error::{self as DieselError, QueryBuilderError},
|
|
|
|
|
},
|
2024-02-19 17:41:28 +00:00
|
|
|
|
sql_types::{self, Timestamptz},
|
2023-08-24 15:27:00 +00:00
|
|
|
|
IntoSql,
|
2022-05-03 17:44:13 +00:00
|
|
|
|
};
|
2022-11-09 10:05:00 +00:00
|
|
|
|
use diesel_async::{
|
|
|
|
|
pg::AsyncPgConnection,
|
2023-02-28 21:45:37 +00:00
|
|
|
|
pooled_connection::{
|
2024-02-12 15:44:29 +00:00
|
|
|
|
deadpool::{Hook, HookError, Object as PooledConnection, Pool},
|
2023-02-28 21:45:37 +00:00
|
|
|
|
AsyncDieselConnectionManager,
|
2023-11-27 09:31:19 +00:00
|
|
|
|
ManagerConfig,
|
2023-02-28 21:45:37 +00:00
|
|
|
|
},
|
2024-07-02 15:23:21 +00:00
|
|
|
|
AsyncConnection,
|
|
|
|
|
RunQueryDsl,
|
2022-11-09 10:05:00 +00:00
|
|
|
|
};
|
2023-07-28 08:36:50 +00:00
|
|
|
|
use futures_util::{future::BoxFuture, Future, FutureExt};
|
2024-01-24 15:50:11 +00:00
|
|
|
|
use i_love_jesus::CursorKey;
|
2023-07-10 14:50:07 +00:00
|
|
|
|
use lemmy_utils::{
|
2024-04-10 14:14:11 +00:00
|
|
|
|
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
2023-10-25 15:34:38 +00:00
|
|
|
|
settings::SETTINGS,
|
2024-09-16 15:15:41 +00:00
|
|
|
|
utils::validation::clean_url,
|
2023-07-10 14:50:07 +00:00
|
|
|
|
};
|
2022-05-03 17:44:13 +00:00
|
|
|
|
use regex::Regex;
|
2023-06-26 08:25:38 +00:00
|
|
|
|
use rustls::{
|
2024-05-03 20:06:14 +00:00
|
|
|
|
client::danger::{
|
|
|
|
|
DangerousClientConfigBuilder,
|
|
|
|
|
HandshakeSignatureValid,
|
|
|
|
|
ServerCertVerified,
|
|
|
|
|
ServerCertVerifier,
|
|
|
|
|
},
|
|
|
|
|
crypto::{self, verify_tls12_signature, verify_tls13_signature},
|
|
|
|
|
pki_types::{CertificateDer, ServerName, UnixTime},
|
|
|
|
|
ClientConfig,
|
|
|
|
|
DigitallySignedStruct,
|
|
|
|
|
SignatureScheme,
|
2023-06-26 08:25:38 +00:00
|
|
|
|
};
|
|
|
|
|
use std::{
|
2023-07-11 13:09:59 +00:00
|
|
|
|
ops::{Deref, DerefMut},
|
2024-07-30 14:11:39 +00:00
|
|
|
|
sync::{Arc, LazyLock},
|
2024-05-03 20:06:14 +00:00
|
|
|
|
time::Duration,
|
2023-06-26 08:25:38 +00:00
|
|
|
|
};
|
2024-04-18 00:58:44 +00:00
|
|
|
|
use tracing::error;
|
2022-05-03 17:44:13 +00:00
|
|
|
|
use url::Url;
|
|
|
|
|
|
2022-07-08 10:21:33 +00:00
|
|
|
|
const FETCH_LIMIT_DEFAULT: i64 = 10;
|
|
|
|
|
pub const FETCH_LIMIT_MAX: i64 = 50;
|
2023-12-11 10:24:51 +00:00
|
|
|
|
pub const SITEMAP_LIMIT: i64 = 50000;
|
2024-03-18 09:36:49 +00:00
|
|
|
|
pub const SITEMAP_DAYS: Option<TimeDelta> = TimeDelta::try_days(31);
|
2023-11-24 01:39:46 +00:00
|
|
|
|
pub const RANK_DEFAULT: f64 = 0.0001;
|
2022-07-08 10:21:33 +00:00
|
|
|
|
|
2023-07-11 13:09:59 +00:00
|
|
|
|
pub type ActualDbPool = Pool<AsyncPgConnection>;
|
2022-11-09 10:05:00 +00:00
|
|
|
|
|
2024-05-23 12:46:26 +00:00
|
|
|
|
/// References a pool or connection. Functions must take `&mut DbPool<'_>` to allow implicit
|
|
|
|
|
/// reborrowing.
|
2023-07-11 13:09:59 +00:00
|
|
|
|
///
|
|
|
|
|
/// https://github.com/rust-lang/rfcs/issues/1403
|
|
|
|
|
pub enum DbPool<'a> {
|
|
|
|
|
Pool(&'a ActualDbPool),
|
|
|
|
|
Conn(&'a mut AsyncPgConnection),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub enum DbConn<'a> {
|
|
|
|
|
Pool(PooledConnection<AsyncPgConnection>),
|
|
|
|
|
Conn(&'a mut AsyncPgConnection),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn get_conn<'a, 'b: 'a>(pool: &'a mut DbPool<'b>) -> Result<DbConn<'a>, DieselError> {
|
|
|
|
|
Ok(match pool {
|
|
|
|
|
DbPool::Pool(pool) => DbConn::Pool(pool.get().await.map_err(|e| QueryBuilderError(e.into()))?),
|
|
|
|
|
DbPool::Conn(conn) => DbConn::Conn(conn),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> Deref for DbConn<'a> {
|
|
|
|
|
type Target = AsyncPgConnection;
|
|
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
|
match self {
|
|
|
|
|
DbConn::Pool(conn) => conn.deref(),
|
|
|
|
|
DbConn::Conn(conn) => conn.deref(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> DerefMut for DbConn<'a> {
|
|
|
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
|
|
|
match self {
|
|
|
|
|
DbConn::Pool(conn) => conn.deref_mut(),
|
|
|
|
|
DbConn::Conn(conn) => conn.deref_mut(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-23 12:46:26 +00:00
|
|
|
|
// Allows functions that take `DbPool<'_>` to be called in a transaction by passing `&mut
|
|
|
|
|
// conn.into()`
|
2023-07-11 13:09:59 +00:00
|
|
|
|
impl<'a> From<&'a mut AsyncPgConnection> for DbPool<'a> {
|
|
|
|
|
fn from(value: &'a mut AsyncPgConnection) -> Self {
|
|
|
|
|
DbPool::Conn(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a, 'b: 'a> From<&'a mut DbConn<'b>> for DbPool<'a> {
|
|
|
|
|
fn from(value: &'a mut DbConn<'b>) -> Self {
|
|
|
|
|
DbPool::Conn(value.deref_mut())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> From<&'a ActualDbPool> for DbPool<'a> {
|
|
|
|
|
fn from(value: &'a ActualDbPool) -> Self {
|
|
|
|
|
DbPool::Pool(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-23 12:46:26 +00:00
|
|
|
|
/// Runs multiple async functions that take `&mut DbPool<'_>` as input and return `Result`. Only
|
|
|
|
|
/// works when the `futures` crate is listed in `Cargo.toml`.
|
2023-07-11 13:09:59 +00:00
|
|
|
|
///
|
|
|
|
|
/// `$pool` is the value given to each function.
|
|
|
|
|
///
|
2024-05-23 12:46:26 +00:00
|
|
|
|
/// A `Result` is returned (not in a `Future`, so don't use `.await`). The `Ok` variant contains a
|
|
|
|
|
/// tuple with the values returned by the given functions.
|
2023-07-11 13:09:59 +00:00
|
|
|
|
///
|
|
|
|
|
/// The functions run concurrently if `$pool` has the `DbPool::Pool` variant.
|
|
|
|
|
#[macro_export]
|
|
|
|
|
macro_rules! try_join_with_pool {
|
|
|
|
|
($pool:ident => ($($func:expr),+)) => {{
|
|
|
|
|
// Check type
|
|
|
|
|
let _: &mut $crate::utils::DbPool<'_> = $pool;
|
|
|
|
|
|
|
|
|
|
match $pool {
|
|
|
|
|
// Run concurrently with `try_join`
|
|
|
|
|
$crate::utils::DbPool::Pool(__pool) => ::futures::try_join!(
|
|
|
|
|
$(async {
|
|
|
|
|
let mut __dbpool = $crate::utils::DbPool::Pool(__pool);
|
|
|
|
|
($func)(&mut __dbpool).await
|
|
|
|
|
}),+
|
|
|
|
|
),
|
|
|
|
|
// Run sequentially
|
|
|
|
|
$crate::utils::DbPool::Conn(__conn) => async {
|
|
|
|
|
Ok(($({
|
|
|
|
|
let mut __dbpool = $crate::utils::DbPool::Conn(__conn);
|
|
|
|
|
// `?` prevents the error type from being inferred in an `async` block, so `match` is used instead
|
|
|
|
|
match ($func)(&mut __dbpool).await {
|
|
|
|
|
::core::result::Result::Ok(__v) => __v,
|
|
|
|
|
::core::result::Result::Err(__v) => return ::core::result::Result::Err(__v),
|
|
|
|
|
}
|
|
|
|
|
}),+))
|
|
|
|
|
}.await,
|
|
|
|
|
}
|
|
|
|
|
}};
|
2022-11-09 10:05:00 +00:00
|
|
|
|
}
|
2022-05-03 17:44:13 +00:00
|
|
|
|
|
2024-01-24 15:50:11 +00:00
|
|
|
|
pub struct ReverseTimestampKey<K>(pub K);
|
|
|
|
|
|
|
|
|
|
impl<K, C> CursorKey<C> for ReverseTimestampKey<K>
|
|
|
|
|
where
|
|
|
|
|
K: CursorKey<C, SqlType = Timestamptz>,
|
|
|
|
|
{
|
|
|
|
|
type SqlType = sql_types::BigInt;
|
|
|
|
|
type CursorValue = functions::reverse_timestamp_sort::HelperType<K::CursorValue>;
|
|
|
|
|
type SqlValue = functions::reverse_timestamp_sort::HelperType<K::SqlValue>;
|
|
|
|
|
|
|
|
|
|
fn get_cursor_value(cursor: &C) -> Self::CursorValue {
|
|
|
|
|
functions::reverse_timestamp_sort(K::get_cursor_value(cursor))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn get_sql_value() -> Self::SqlValue {
|
|
|
|
|
functions::reverse_timestamp_sort(K::get_sql_value())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-24 15:22:33 +00:00
|
|
|
|
/// Includes an SQL comment before `T`, which can be used to label auto_explain output
|
|
|
|
|
#[derive(QueryId)]
|
|
|
|
|
pub struct Commented<T> {
|
|
|
|
|
comment: String,
|
|
|
|
|
inner: T,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T> Commented<T> {
|
|
|
|
|
pub fn new(inner: T) -> Self {
|
|
|
|
|
Commented {
|
|
|
|
|
comment: String::new(),
|
|
|
|
|
inner,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Adds `text` to the comment if `condition` is true
|
|
|
|
|
pub fn text_if(mut self, text: &str, condition: bool) -> Self {
|
|
|
|
|
if condition {
|
|
|
|
|
if !self.comment.is_empty() {
|
|
|
|
|
self.comment.push_str(", ");
|
|
|
|
|
}
|
|
|
|
|
self.comment.push_str(text);
|
|
|
|
|
}
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Adds `text` to the comment
|
|
|
|
|
pub fn text(self, text: &str) -> Self {
|
|
|
|
|
self.text_if(text, true)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T: Query> Query for Commented<T> {
|
|
|
|
|
type SqlType = T::SqlType;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T: QueryFragment<Pg>> QueryFragment<Pg> for Commented<T> {
|
|
|
|
|
fn walk_ast<'b>(
|
|
|
|
|
&'b self,
|
|
|
|
|
mut out: diesel::query_builder::AstPass<'_, 'b, Pg>,
|
|
|
|
|
) -> Result<(), DieselError> {
|
|
|
|
|
for line in self.comment.lines() {
|
|
|
|
|
out.push_sql("\n-- ");
|
|
|
|
|
out.push_sql(line);
|
|
|
|
|
}
|
|
|
|
|
out.push_sql("\n");
|
|
|
|
|
self.inner.walk_ast(out.reborrow())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T: LimitDsl> LimitDsl for Commented<T> {
|
|
|
|
|
type Output = Commented<T::Output>;
|
|
|
|
|
|
|
|
|
|
fn limit(self, limit: i64) -> Self::Output {
|
|
|
|
|
Commented {
|
|
|
|
|
comment: self.comment,
|
|
|
|
|
inner: self.inner.limit(limit),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-03 17:44:13 +00:00
|
|
|
|
pub fn fuzzy_search(q: &str) -> String {
|
2024-02-18 14:12:56 +00:00
|
|
|
|
let replaced = q
|
|
|
|
|
.replace('\\', "\\\\")
|
|
|
|
|
.replace('%', "\\%")
|
|
|
|
|
.replace('_', "\\_")
|
|
|
|
|
.replace(' ', "%");
|
2023-01-30 19:17:24 +00:00
|
|
|
|
format!("%{replaced}%")
|
2022-05-03 17:44:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-07-08 10:21:33 +00:00
|
|
|
|
pub fn limit_and_offset(
|
|
|
|
|
page: Option<i64>,
|
|
|
|
|
limit: Option<i64>,
|
|
|
|
|
) -> Result<(i64, i64), diesel::result::Error> {
|
|
|
|
|
let page = match page {
|
|
|
|
|
Some(page) => {
|
|
|
|
|
if page < 1 {
|
|
|
|
|
return Err(QueryBuilderError("Page is < 1".into()));
|
|
|
|
|
}
|
2024-02-26 14:47:10 +00:00
|
|
|
|
page
|
2022-07-08 10:21:33 +00:00
|
|
|
|
}
|
|
|
|
|
None => 1,
|
|
|
|
|
};
|
|
|
|
|
let limit = match limit {
|
|
|
|
|
Some(limit) => {
|
|
|
|
|
if !(1..=FETCH_LIMIT_MAX).contains(&limit) {
|
|
|
|
|
return Err(QueryBuilderError(
|
2023-01-30 19:17:24 +00:00
|
|
|
|
format!("Fetch limit is > {FETCH_LIMIT_MAX}").into(),
|
2022-07-08 10:21:33 +00:00
|
|
|
|
));
|
|
|
|
|
}
|
2024-02-26 14:47:10 +00:00
|
|
|
|
limit
|
2022-07-08 10:21:33 +00:00
|
|
|
|
}
|
|
|
|
|
None => FETCH_LIMIT_DEFAULT,
|
|
|
|
|
};
|
2022-05-03 17:44:13 +00:00
|
|
|
|
let offset = limit * (page - 1);
|
2022-07-08 10:21:33 +00:00
|
|
|
|
Ok((limit, offset))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn limit_and_offset_unlimited(page: Option<i64>, limit: Option<i64>) -> (i64, i64) {
|
|
|
|
|
let limit = limit.unwrap_or(FETCH_LIMIT_DEFAULT);
|
|
|
|
|
let offset = limit * (page.unwrap_or(1) - 1);
|
2022-05-03 17:44:13 +00:00
|
|
|
|
(limit, offset)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn is_email_regex(test: &str) -> bool {
|
|
|
|
|
EMAIL_REGEX.is_match(test)
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-18 12:52:33 +00:00
|
|
|
|
/// Takes an API optional text input, and converts it to an optional diesel DB update.
|
2024-06-06 13:55:08 +00:00
|
|
|
|
pub fn diesel_string_update(opt: Option<&str>) -> Option<Option<String>> {
|
2022-05-03 17:44:13 +00:00
|
|
|
|
match opt {
|
|
|
|
|
// An empty string is an erase
|
2024-06-06 13:55:08 +00:00
|
|
|
|
Some("") => Some(None),
|
|
|
|
|
Some(str) => Some(Some(str.into())),
|
2022-05-03 17:44:13 +00:00
|
|
|
|
None => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-18 12:52:33 +00:00
|
|
|
|
/// Takes an API optional text input, and converts it to an optional diesel DB update (for non
|
|
|
|
|
/// nullable properties).
|
|
|
|
|
pub fn diesel_required_string_update(opt: Option<&str>) -> Option<String> {
|
|
|
|
|
match opt {
|
|
|
|
|
// An empty string is no change
|
|
|
|
|
Some("") => None,
|
|
|
|
|
Some(str) => Some(str.into()),
|
|
|
|
|
None => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-06 13:55:08 +00:00
|
|
|
|
/// Takes an optional API URL-type input, and converts it to an optional diesel DB update.
|
|
|
|
|
/// Also cleans the url params.
|
|
|
|
|
pub fn diesel_url_update(opt: Option<&str>) -> LemmyResult<Option<Option<DbUrl>>> {
|
|
|
|
|
match opt {
|
2022-05-03 17:44:13 +00:00
|
|
|
|
// An empty string is an erase
|
|
|
|
|
Some("") => Ok(Some(None)),
|
2023-07-10 14:50:07 +00:00
|
|
|
|
Some(str_url) => Url::parse(str_url)
|
2024-09-16 15:15:41 +00:00
|
|
|
|
.map(|u| Some(Some(clean_url(&u).into())))
|
2023-07-10 14:50:07 +00:00
|
|
|
|
.with_lemmy_type(LemmyErrorType::InvalidUrl),
|
2022-05-03 17:44:13 +00:00
|
|
|
|
None => Ok(None),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-18 12:52:33 +00:00
|
|
|
|
/// Takes an optional API URL-type input, and converts it to an optional diesel DB update (for non
|
|
|
|
|
/// nullable properties). Also cleans the url params.
|
|
|
|
|
pub fn diesel_required_url_update(opt: Option<&str>) -> LemmyResult<Option<DbUrl>> {
|
|
|
|
|
match opt {
|
|
|
|
|
// An empty string is no change
|
|
|
|
|
Some("") => Ok(None),
|
|
|
|
|
Some(str_url) => Url::parse(str_url)
|
|
|
|
|
.map(|u| Some(clean_url(&u).into()))
|
|
|
|
|
.with_lemmy_type(LemmyErrorType::InvalidUrl),
|
|
|
|
|
None => Ok(None),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-06 13:55:08 +00:00
|
|
|
|
/// Takes an optional API URL-type input, and converts it to an optional diesel DB create.
|
|
|
|
|
/// Also cleans the url params.
|
|
|
|
|
pub fn diesel_url_create(opt: Option<&str>) -> LemmyResult<Option<DbUrl>> {
|
|
|
|
|
match opt {
|
2023-07-10 14:50:07 +00:00
|
|
|
|
Some(str_url) => Url::parse(str_url)
|
2024-09-16 15:15:41 +00:00
|
|
|
|
.map(|u| Some(clean_url(&u).into()))
|
2023-07-10 14:50:07 +00:00
|
|
|
|
.with_lemmy_type(LemmyErrorType::InvalidUrl),
|
2022-10-27 09:24:07 +00:00
|
|
|
|
None => Ok(None),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-26 08:25:38 +00:00
|
|
|
|
fn establish_connection(config: &str) -> BoxFuture<ConnectionResult<AsyncPgConnection>> {
|
|
|
|
|
let fut = async {
|
2024-07-02 15:23:21 +00:00
|
|
|
|
// We only support TLS with sslmode=require currently
|
|
|
|
|
let mut conn = if config.contains("sslmode=require") {
|
|
|
|
|
let rustls_config = DangerousClientConfigBuilder {
|
|
|
|
|
cfg: ClientConfig::builder(),
|
2023-06-26 08:25:38 +00:00
|
|
|
|
}
|
2024-07-02 15:23:21 +00:00
|
|
|
|
.with_custom_certificate_verifier(Arc::new(NoCertVerifier {}))
|
|
|
|
|
.with_no_client_auth();
|
|
|
|
|
|
|
|
|
|
let tls = tokio_postgres_rustls::MakeRustlsConnect::new(rustls_config);
|
|
|
|
|
let (client, conn) = tokio_postgres::connect(config, tls)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| ConnectionError::BadConnection(e.to_string()))?;
|
|
|
|
|
tokio::spawn(async move {
|
|
|
|
|
if let Err(e) = conn.await {
|
|
|
|
|
error!("Database connection failed: {e}");
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
AsyncPgConnection::try_from(client).await?
|
|
|
|
|
} else {
|
|
|
|
|
AsyncPgConnection::establish(config).await?
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
diesel::select((
|
|
|
|
|
// Change geqo_threshold back to default value if it was changed, so it's higher than the
|
|
|
|
|
// collapse limits
|
|
|
|
|
functions::set_config("geqo_threshold", "12", false),
|
|
|
|
|
// Change collapse limits from 8 to 11 so the query planner can find a better table join
|
|
|
|
|
// order for more complicated queries
|
|
|
|
|
functions::set_config("from_collapse_limit", "11", false),
|
|
|
|
|
functions::set_config("join_collapse_limit", "11", false),
|
|
|
|
|
// Set `lemmy.protocol_and_hostname` so triggers can use it
|
|
|
|
|
functions::set_config(
|
|
|
|
|
"lemmy.protocol_and_hostname",
|
|
|
|
|
SETTINGS.get_protocol_and_hostname(),
|
|
|
|
|
false,
|
|
|
|
|
),
|
|
|
|
|
))
|
|
|
|
|
.execute(&mut conn)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(ConnectionError::CouldntSetupConfiguration)?;
|
2024-02-12 15:44:29 +00:00
|
|
|
|
Ok(conn)
|
2023-06-26 08:25:38 +00:00
|
|
|
|
};
|
|
|
|
|
fut.boxed()
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-03 20:06:14 +00:00
|
|
|
|
#[derive(Debug)]
|
2023-06-26 08:25:38 +00:00
|
|
|
|
struct NoCertVerifier {}
|
|
|
|
|
|
|
|
|
|
impl ServerCertVerifier for NoCertVerifier {
|
|
|
|
|
fn verify_server_cert(
|
|
|
|
|
&self,
|
2024-05-03 20:06:14 +00:00
|
|
|
|
_end_entity: &CertificateDer,
|
|
|
|
|
_intermediates: &[CertificateDer],
|
2023-06-26 08:25:38 +00:00
|
|
|
|
_server_name: &ServerName,
|
2024-05-03 20:06:14 +00:00
|
|
|
|
_ocsp: &[u8],
|
|
|
|
|
_now: UnixTime,
|
2023-06-26 08:25:38 +00:00
|
|
|
|
) -> Result<ServerCertVerified, rustls::Error> {
|
|
|
|
|
// Will verify all (even invalid) certs without any checks (sslmode=require)
|
|
|
|
|
Ok(ServerCertVerified::assertion())
|
|
|
|
|
}
|
2024-05-03 20:06:14 +00:00
|
|
|
|
|
|
|
|
|
fn verify_tls12_signature(
|
|
|
|
|
&self,
|
|
|
|
|
message: &[u8],
|
|
|
|
|
cert: &CertificateDer,
|
|
|
|
|
dss: &DigitallySignedStruct,
|
|
|
|
|
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
|
|
|
|
verify_tls12_signature(
|
|
|
|
|
message,
|
|
|
|
|
cert,
|
|
|
|
|
dss,
|
|
|
|
|
&crypto::ring::default_provider().signature_verification_algorithms,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn verify_tls13_signature(
|
|
|
|
|
&self,
|
|
|
|
|
message: &[u8],
|
|
|
|
|
cert: &CertificateDer,
|
|
|
|
|
dss: &DigitallySignedStruct,
|
|
|
|
|
) -> Result<HandshakeSignatureValid, rustls::Error> {
|
|
|
|
|
verify_tls13_signature(
|
|
|
|
|
message,
|
|
|
|
|
cert,
|
|
|
|
|
dss,
|
|
|
|
|
&crypto::ring::default_provider().signature_verification_algorithms,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
|
|
|
|
|
crypto::ring::default_provider()
|
|
|
|
|
.signature_verification_algorithms
|
|
|
|
|
.supported_schemes()
|
|
|
|
|
}
|
2023-06-26 08:25:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-04-10 14:14:11 +00:00
|
|
|
|
pub async fn build_db_pool() -> LemmyResult<ActualDbPool> {
|
2023-10-25 15:34:38 +00:00
|
|
|
|
let db_url = SETTINGS.get_database_url();
|
2024-07-02 15:23:21 +00:00
|
|
|
|
// diesel-async does not support any TLS connections out of the box, so we need to manually
|
|
|
|
|
// provide a setup function which handles creating the connection
|
|
|
|
|
let mut config = ManagerConfig::default();
|
|
|
|
|
config.custom_setup = Box::new(establish_connection);
|
|
|
|
|
let manager = AsyncDieselConnectionManager::<AsyncPgConnection>::new_with_config(&db_url, config);
|
2023-10-25 15:34:38 +00:00
|
|
|
|
let pool = Pool::builder(manager)
|
|
|
|
|
.max_size(SETTINGS.database.pool_size)
|
|
|
|
|
.runtime(Runtime::Tokio1)
|
2024-05-23 12:46:26 +00:00
|
|
|
|
// Limit connection age to prevent use of prepared statements that have query plans based on
|
|
|
|
|
// very old statistics
|
2024-02-12 15:44:29 +00:00
|
|
|
|
.pre_recycle(Hook::sync_fn(|_conn, metrics| {
|
2024-05-23 12:46:26 +00:00
|
|
|
|
// Preventing the first recycle can cause an infinite loop when trying to get a new connection
|
|
|
|
|
// from the pool
|
2024-02-12 15:44:29 +00:00
|
|
|
|
let conn_was_used = metrics.recycled.is_some();
|
|
|
|
|
if metrics.age() > Duration::from_secs(3 * 24 * 60 * 60) && conn_was_used {
|
|
|
|
|
Err(HookError::Continue(None))
|
|
|
|
|
} else {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}))
|
2023-10-25 15:34:38 +00:00
|
|
|
|
.build()?;
|
2022-11-09 10:05:00 +00:00
|
|
|
|
|
2024-04-18 00:58:44 +00:00
|
|
|
|
crate::schema_setup::run(&db_url)?;
|
2023-10-25 15:34:38 +00:00
|
|
|
|
|
|
|
|
|
Ok(pool)
|
2022-11-09 10:05:00 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-10-25 15:34:38 +00:00
|
|
|
|
pub async fn build_db_pool_for_tests() -> ActualDbPool {
|
|
|
|
|
build_db_pool().await.expect("db pool missing")
|
2022-05-03 17:44:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-24 15:27:00 +00:00
|
|
|
|
pub fn naive_now() -> DateTime<Utc> {
|
2023-10-25 15:34:38 +00:00
|
|
|
|
Utc::now()
|
2022-05-03 17:44:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-09-19 08:03:58 +00:00
|
|
|
|
pub fn post_to_comment_sort_type(sort: PostSortType) -> CommentSortType {
|
|
|
|
|
use PostSortType::*;
|
2022-07-30 03:55:59 +00:00
|
|
|
|
match sort {
|
2024-09-19 08:03:58 +00:00
|
|
|
|
Active | Hot | Scaled => CommentSortType::Hot,
|
|
|
|
|
New | NewComments | MostComments => CommentSortType::New,
|
|
|
|
|
Old => CommentSortType::Old,
|
|
|
|
|
Controversial => CommentSortType::Controversial,
|
|
|
|
|
TopHour | TopSixHour | TopTwelveHour | TopDay | TopAll | TopWeek | TopYear | TopMonth
|
|
|
|
|
| TopThreeMonths | TopSixMonths | TopNineMonths => CommentSortType::Top,
|
2022-07-30 03:55:59 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-30 14:11:39 +00:00
|
|
|
|
static EMAIL_REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
2022-05-03 17:44:13 +00:00
|
|
|
|
Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$")
|
|
|
|
|
.expect("compile email regex")
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
pub mod functions {
|
2024-07-02 15:23:21 +00:00
|
|
|
|
use diesel::sql_types::{BigInt, Bool, Text, Timestamptz};
|
2022-05-03 17:44:13 +00:00
|
|
|
|
|
|
|
|
|
sql_function! {
|
2024-04-18 00:58:44 +00:00
|
|
|
|
#[sql_name = "r.hot_rank"]
|
2023-09-06 17:43:27 +00:00
|
|
|
|
fn hot_rank(score: BigInt, time: Timestamptz) -> Double;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sql_function! {
|
2024-04-18 00:58:44 +00:00
|
|
|
|
#[sql_name = "r.scaled_rank"]
|
2023-09-06 17:43:27 +00:00
|
|
|
|
fn scaled_rank(score: BigInt, time: Timestamptz, users_active_month: BigInt) -> Double;
|
2022-05-03 17:44:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-26 17:07:05 +00:00
|
|
|
|
sql_function! {
|
2024-04-18 00:58:44 +00:00
|
|
|
|
#[sql_name = "r.controversy_rank"]
|
2023-07-26 17:07:05 +00:00
|
|
|
|
fn controversy_rank(upvotes: BigInt, downvotes: BigInt, score: BigInt) -> Double;
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-24 15:50:11 +00:00
|
|
|
|
sql_function!(fn reverse_timestamp_sort(time: Timestamptz) -> BigInt);
|
|
|
|
|
|
2022-05-03 17:44:13 +00:00
|
|
|
|
sql_function!(fn lower(x: Text) -> Text);
|
2023-09-09 16:25:03 +00:00
|
|
|
|
|
|
|
|
|
// really this function is variadic, this just adds the two-argument version
|
|
|
|
|
sql_function!(fn coalesce<T: diesel::sql_types::SqlType + diesel::sql_types::SingleValue>(x: diesel::sql_types::Nullable<T>, y: T) -> T);
|
2024-07-02 15:23:21 +00:00
|
|
|
|
|
|
|
|
|
sql_function!(fn set_config(setting_name: Text, new_value: Text, is_local: Bool) -> Text);
|
2022-05-03 17:44:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-06-20 06:17:54 +00:00
|
|
|
|
pub const DELETED_REPLACEMENT_TEXT: &str = "*Permanently Deleted*";
|
|
|
|
|
|
2023-08-24 15:27:00 +00:00
|
|
|
|
pub fn now() -> AsExprOf<diesel::dsl::now, diesel::sql_types::Timestamptz> {
|
|
|
|
|
// https://github.com/diesel-rs/diesel/issues/1514
|
|
|
|
|
diesel::dsl::now.into_sql::<Timestamptz>()
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-28 08:36:50 +00:00
|
|
|
|
pub type ResultFuture<'a, T> = BoxFuture<'a, Result<T, DieselError>>;
|
|
|
|
|
|
2023-08-31 13:26:10 +00:00
|
|
|
|
pub trait ReadFn<'a, T, Args>: Fn(DbConn<'a>, Args) -> ResultFuture<'a, T> {}
|
2023-07-28 08:36:50 +00:00
|
|
|
|
|
2023-08-31 13:26:10 +00:00
|
|
|
|
impl<'a, T, Args, F: Fn(DbConn<'a>, Args) -> ResultFuture<'a, T>> ReadFn<'a, T, Args> for F {}
|
2023-07-28 08:36:50 +00:00
|
|
|
|
|
2023-08-31 13:26:10 +00:00
|
|
|
|
pub trait ListFn<'a, T, Args>: Fn(DbConn<'a>, Args) -> ResultFuture<'a, Vec<T>> {}
|
2023-07-28 08:36:50 +00:00
|
|
|
|
|
2023-08-31 13:26:10 +00:00
|
|
|
|
impl<'a, T, Args, F: Fn(DbConn<'a>, Args) -> ResultFuture<'a, Vec<T>>> ListFn<'a, T, Args> for F {}
|
2023-07-28 08:36:50 +00:00
|
|
|
|
|
2024-05-23 12:46:26 +00:00
|
|
|
|
/// Allows read and list functions to capture a shared closure that has an inferred return type,
|
|
|
|
|
/// which is useful for join logic
|
2023-07-28 08:36:50 +00:00
|
|
|
|
pub struct Queries<RF, LF> {
|
|
|
|
|
pub read_fn: RF,
|
|
|
|
|
pub list_fn: LF,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// `()` is used to prevent type inference error
|
|
|
|
|
impl Queries<(), ()> {
|
|
|
|
|
pub fn new<'a, RFut, LFut, RT, LT, RA, LA, RF2, LF2>(
|
|
|
|
|
read_fn: RF2,
|
|
|
|
|
list_fn: LF2,
|
|
|
|
|
) -> Queries<impl ReadFn<'a, RT, RA>, impl ListFn<'a, LT, LA>>
|
|
|
|
|
where
|
2023-08-31 13:26:10 +00:00
|
|
|
|
RFut: Future<Output = Result<RT, DieselError>> + Sized + Send + 'a,
|
|
|
|
|
LFut: Future<Output = Result<Vec<LT>, DieselError>> + Sized + Send + 'a,
|
2023-07-28 08:36:50 +00:00
|
|
|
|
RF2: Fn(DbConn<'a>, RA) -> RFut,
|
|
|
|
|
LF2: Fn(DbConn<'a>, LA) -> LFut,
|
|
|
|
|
{
|
|
|
|
|
Queries {
|
|
|
|
|
read_fn: move |conn, args| read_fn(conn, args).boxed(),
|
|
|
|
|
list_fn: move |conn, args| list_fn(conn, args).boxed(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<RF, LF> Queries<RF, LF> {
|
|
|
|
|
pub async fn read<'a, T, Args>(
|
|
|
|
|
self,
|
|
|
|
|
pool: &'a mut DbPool<'_>,
|
|
|
|
|
args: Args,
|
2024-09-23 15:26:50 +00:00
|
|
|
|
) -> Result<T, DieselError>
|
2023-07-28 08:36:50 +00:00
|
|
|
|
where
|
|
|
|
|
RF: ReadFn<'a, T, Args>,
|
|
|
|
|
{
|
|
|
|
|
let conn = get_conn(pool).await?;
|
2024-09-23 15:26:50 +00:00
|
|
|
|
(self.read_fn)(conn, args).await
|
2023-07-28 08:36:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn list<'a, T, Args>(
|
|
|
|
|
self,
|
|
|
|
|
pool: &'a mut DbPool<'_>,
|
|
|
|
|
args: Args,
|
|
|
|
|
) -> Result<Vec<T>, DieselError>
|
|
|
|
|
where
|
|
|
|
|
LF: ListFn<'a, T, Args>,
|
|
|
|
|
{
|
|
|
|
|
let conn = get_conn(pool).await?;
|
2023-08-31 13:26:10 +00:00
|
|
|
|
(self.list_fn)(conn, args).await
|
2023-07-28 08:36:50 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-03 17:44:13 +00:00
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
2023-07-17 15:04:14 +00:00
|
|
|
|
|
2024-02-26 14:47:10 +00:00
|
|
|
|
use super::*;
|
2024-01-04 09:47:18 +00:00
|
|
|
|
use pretty_assertions::assert_eq;
|
2022-05-03 17:44:13 +00:00
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_fuzzy_search() {
|
|
|
|
|
let test = "This %is% _a_ fuzzy search";
|
|
|
|
|
assert_eq!(
|
|
|
|
|
fuzzy_search(test),
|
|
|
|
|
"%This%\\%is\\%%\\_a\\_%fuzzy%search%".to_string()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_email() {
|
|
|
|
|
assert!(is_email_regex("gush@gmail.com"));
|
|
|
|
|
assert!(!is_email_regex("nada_neutho"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_diesel_option_overwrite() {
|
2024-06-06 13:55:08 +00:00
|
|
|
|
assert_eq!(diesel_string_update(None), None);
|
|
|
|
|
assert_eq!(diesel_string_update(Some("")), Some(None));
|
2022-05-03 17:44:13 +00:00
|
|
|
|
assert_eq!(
|
2024-06-06 13:55:08 +00:00
|
|
|
|
diesel_string_update(Some("test")),
|
2022-05-03 17:44:13 +00:00
|
|
|
|
Some(Some("test".to_string()))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2024-06-06 13:55:08 +00:00
|
|
|
|
fn test_diesel_option_overwrite_to_url() -> LemmyResult<()> {
|
|
|
|
|
assert!(matches!(diesel_url_update(None), Ok(None)));
|
|
|
|
|
assert!(matches!(diesel_url_update(Some("")), Ok(Some(None))));
|
|
|
|
|
assert!(diesel_url_update(Some("invalid_url")).is_err());
|
2022-05-03 17:44:13 +00:00
|
|
|
|
let example_url = "https://example.com";
|
|
|
|
|
assert!(matches!(
|
2024-06-06 13:55:08 +00:00
|
|
|
|
diesel_url_update(Some(example_url)),
|
|
|
|
|
Ok(Some(Some(url))) if url == Url::parse(example_url)?.into()
|
2022-05-03 17:44:13 +00:00
|
|
|
|
));
|
2024-06-06 13:55:08 +00:00
|
|
|
|
Ok(())
|
2022-05-03 17:44:13 +00:00
|
|
|
|
}
|
|
|
|
|
}
|