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