]> Untitled Git - lemmy.git/blob - crates/db_schema/src/utils.rs
585430572256d0eae12aa2096487b1607db96df1
[lemmy.git] / crates / db_schema / src / utils.rs
1 use crate::newtypes::DbUrl;
2 use chrono::NaiveDateTime;
3 use diesel::{
4   backend::Backend,
5   deserialize::FromSql,
6   serialize::{Output, ToSql},
7   sql_types::Text,
8   Connection,
9   PgConnection,
10 };
11 use lemmy_apub_lib::{object_id::ObjectId, traits::ApubObject};
12 use lemmy_utils::LemmyError;
13 use once_cell::sync::Lazy;
14 use regex::Regex;
15 use serde::{Deserialize, Serialize};
16 use std::{env, env::VarError, io::Write};
17 use url::Url;
18
19 pub type DbPool = diesel::r2d2::Pool<diesel::r2d2::ConnectionManager<diesel::PgConnection>>;
20
21 pub fn get_database_url_from_env() -> Result<String, VarError> {
22   env::var("LEMMY_DATABASE_URL")
23 }
24
25 #[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy)]
26 pub enum SortType {
27   Active,
28   Hot,
29   New,
30   TopDay,
31   TopWeek,
32   TopMonth,
33   TopYear,
34   TopAll,
35   MostComments,
36   NewComments,
37 }
38
39 #[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
40 pub enum ListingType {
41   All,
42   Local,
43   Subscribed,
44   Community,
45 }
46
47 #[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy)]
48 pub enum SearchType {
49   All,
50   Comments,
51   Posts,
52   Communities,
53   Users,
54   Url,
55 }
56
57 pub fn from_opt_str_to_opt_enum<T: std::str::FromStr>(opt: &Option<String>) -> Option<T> {
58   opt.as_ref().and_then(|t| T::from_str(t).ok())
59 }
60
61 pub fn fuzzy_search(q: &str) -> String {
62   let replaced = q.replace('%', "\\%").replace('_', "\\_").replace(' ', "%");
63   format!("%{}%", replaced)
64 }
65
66 pub fn limit_and_offset(page: Option<i64>, limit: Option<i64>) -> (i64, i64) {
67   let page = page.unwrap_or(1);
68   let limit = limit.unwrap_or(10);
69   let offset = limit * (page - 1);
70   (limit, offset)
71 }
72
73 pub fn is_email_regex(test: &str) -> bool {
74   EMAIL_REGEX.is_match(test)
75 }
76
77 pub fn diesel_option_overwrite(opt: &Option<String>) -> Option<Option<String>> {
78   match opt {
79     // An empty string is an erase
80     Some(unwrapped) => {
81       if !unwrapped.eq("") {
82         Some(Some(unwrapped.to_owned()))
83       } else {
84         Some(None)
85       }
86     }
87     None => None,
88   }
89 }
90
91 pub fn diesel_option_overwrite_to_url(
92   opt: &Option<String>,
93 ) -> Result<Option<Option<DbUrl>>, LemmyError> {
94   match opt.as_ref().map(|s| s.as_str()) {
95     // An empty string is an erase
96     Some("") => Ok(Some(None)),
97     Some(str_url) => match Url::parse(str_url) {
98       Ok(url) => Ok(Some(Some(url.into()))),
99       Err(e) => Err(LemmyError::from_error_message(e, "invalid_url")),
100     },
101     None => Ok(None),
102   }
103 }
104
105 embed_migrations!();
106
107 pub fn establish_unpooled_connection() -> PgConnection {
108   let db_url = match get_database_url_from_env() {
109     Ok(url) => url,
110     Err(e) => panic!(
111       "Failed to read database URL from env var LEMMY_DATABASE_URL: {}",
112       e
113     ),
114   };
115   let conn =
116     PgConnection::establish(&db_url).unwrap_or_else(|_| panic!("Error connecting to {}", db_url));
117   embedded_migrations::run(&conn).expect("load migrations");
118   conn
119 }
120
121 pub fn naive_now() -> NaiveDateTime {
122   chrono::prelude::Utc::now().naive_utc()
123 }
124
125 static EMAIL_REGEX: Lazy<Regex> = Lazy::new(|| {
126   Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$")
127     .expect("compile email regex")
128 });
129
130 pub mod functions {
131   use diesel::sql_types::*;
132
133   sql_function! {
134     fn hot_rank(score: BigInt, time: Timestamp) -> Integer;
135   }
136
137   sql_function!(fn lower(x: Text) -> Text);
138 }
139
140 impl<DB: Backend> ToSql<Text, DB> for DbUrl
141 where
142   String: ToSql<Text, DB>,
143 {
144   fn to_sql<W: Write>(&self, out: &mut Output<W, DB>) -> diesel::serialize::Result {
145     self.0.to_string().to_sql(out)
146   }
147 }
148
149 impl<DB: Backend> FromSql<Text, DB> for DbUrl
150 where
151   String: FromSql<Text, DB>,
152 {
153   fn from_sql(bytes: Option<&DB::RawValue>) -> diesel::deserialize::Result<Self> {
154     let str = String::from_sql(bytes)?;
155     Ok(DbUrl(Url::parse(&str)?))
156   }
157 }
158
159 impl<Kind> From<ObjectId<Kind>> for DbUrl
160 where
161   Kind: ApubObject + Send + 'static,
162   for<'de2> <Kind as ApubObject>::ApubType: serde::Deserialize<'de2>,
163 {
164   fn from(id: ObjectId<Kind>) -> Self {
165     DbUrl(id.into())
166   }
167 }
168
169 #[cfg(test)]
170 mod tests {
171   use super::{fuzzy_search, *};
172   use crate::utils::is_email_regex;
173
174   #[test]
175   fn test_fuzzy_search() {
176     let test = "This %is% _a_ fuzzy search";
177     assert_eq!(
178       fuzzy_search(test),
179       "%This%\\%is\\%%\\_a\\_%fuzzy%search%".to_string()
180     );
181   }
182
183   #[test]
184   fn test_email() {
185     assert!(is_email_regex("gush@gmail.com"));
186     assert!(!is_email_regex("nada_neutho"));
187   }
188
189   #[test]
190   fn test_diesel_option_overwrite() {
191     assert_eq!(diesel_option_overwrite(&None), None);
192     assert_eq!(diesel_option_overwrite(&Some("".to_string())), Some(None));
193     assert_eq!(
194       diesel_option_overwrite(&Some("test".to_string())),
195       Some(Some("test".to_string()))
196     );
197   }
198
199   #[test]
200   fn test_diesel_option_overwrite_to_url() {
201     assert!(matches!(diesel_option_overwrite_to_url(&None), Ok(None)));
202     assert!(matches!(
203       diesel_option_overwrite_to_url(&Some("".to_string())),
204       Ok(Some(None))
205     ));
206     assert!(matches!(
207       diesel_option_overwrite_to_url(&Some("invalid_url".to_string())),
208       Err(_)
209     ));
210     let example_url = "https://example.com";
211     assert!(matches!(
212       diesel_option_overwrite_to_url(&Some(example_url.to_string())),
213       Ok(Some(Some(url))) if url == Url::parse(example_url).unwrap().into()
214     ));
215   }
216 }