]> Untitled Git - lemmy.git/blob - crates/db_queries/src/lib.rs
Running clippy --fix (#1647)
[lemmy.git] / crates / db_queries / src / lib.rs
1 #[macro_use]
2 extern crate diesel;
3 #[macro_use]
4 extern crate strum_macros;
5 #[macro_use]
6 extern crate lazy_static;
7 // this is used in tests
8 #[allow(unused_imports)]
9 #[macro_use]
10 extern crate diesel_migrations;
11
12 #[cfg(test)]
13 extern crate serial_test;
14
15 use diesel::{result::Error, *};
16 use lemmy_db_schema::{CommunityId, DbUrl, PersonId};
17 use lemmy_utils::ApiError;
18 use regex::Regex;
19 use serde::{Deserialize, Serialize};
20 use std::{env, env::VarError};
21 use url::Url;
22
23 pub mod aggregates;
24 pub mod source;
25
26 pub type DbPool = diesel::r2d2::Pool<diesel::r2d2::ConnectionManager<diesel::PgConnection>>;
27
28 pub trait Crud<Form, IdType> {
29   fn create(conn: &PgConnection, form: &Form) -> Result<Self, Error>
30   where
31     Self: Sized;
32   fn read(conn: &PgConnection, id: IdType) -> Result<Self, Error>
33   where
34     Self: Sized;
35   fn update(conn: &PgConnection, id: IdType, form: &Form) -> Result<Self, Error>
36   where
37     Self: Sized;
38   fn delete(_conn: &PgConnection, _id: IdType) -> Result<usize, Error>
39   where
40     Self: Sized,
41   {
42     unimplemented!()
43   }
44 }
45
46 pub trait Followable<Form> {
47   fn follow(conn: &PgConnection, form: &Form) -> Result<Self, Error>
48   where
49     Self: Sized;
50   fn follow_accepted(
51     conn: &PgConnection,
52     community_id: CommunityId,
53     person_id: PersonId,
54   ) -> Result<Self, Error>
55   where
56     Self: Sized;
57   fn unfollow(conn: &PgConnection, form: &Form) -> Result<usize, Error>
58   where
59     Self: Sized;
60   fn has_local_followers(conn: &PgConnection, community_id: CommunityId) -> Result<bool, Error>;
61 }
62
63 pub trait Joinable<Form> {
64   fn join(conn: &PgConnection, form: &Form) -> Result<Self, Error>
65   where
66     Self: Sized;
67   fn leave(conn: &PgConnection, form: &Form) -> Result<usize, Error>
68   where
69     Self: Sized;
70 }
71
72 pub trait Likeable<Form, IdType> {
73   fn like(conn: &PgConnection, form: &Form) -> Result<Self, Error>
74   where
75     Self: Sized;
76   fn remove(conn: &PgConnection, person_id: PersonId, item_id: IdType) -> Result<usize, Error>
77   where
78     Self: Sized;
79 }
80
81 pub trait Bannable<Form> {
82   fn ban(conn: &PgConnection, form: &Form) -> Result<Self, Error>
83   where
84     Self: Sized;
85   fn unban(conn: &PgConnection, form: &Form) -> Result<usize, Error>
86   where
87     Self: Sized;
88 }
89
90 pub trait Saveable<Form> {
91   fn save(conn: &PgConnection, form: &Form) -> Result<Self, Error>
92   where
93     Self: Sized;
94   fn unsave(conn: &PgConnection, form: &Form) -> Result<usize, Error>
95   where
96     Self: Sized;
97 }
98
99 pub trait Readable<Form> {
100   fn mark_as_read(conn: &PgConnection, form: &Form) -> Result<Self, Error>
101   where
102     Self: Sized;
103   fn mark_as_unread(conn: &PgConnection, form: &Form) -> Result<usize, Error>
104   where
105     Self: Sized;
106 }
107
108 pub trait Reportable<Form> {
109   fn report(conn: &PgConnection, form: &Form) -> Result<Self, Error>
110   where
111     Self: Sized;
112   fn resolve(conn: &PgConnection, report_id: i32, resolver_id: PersonId) -> Result<usize, Error>
113   where
114     Self: Sized;
115   fn unresolve(conn: &PgConnection, report_id: i32, resolver_id: PersonId) -> Result<usize, Error>
116   where
117     Self: Sized;
118 }
119
120 pub trait ApubObject<Form> {
121   fn read_from_apub_id(conn: &PgConnection, object_id: &DbUrl) -> Result<Self, Error>
122   where
123     Self: Sized;
124   fn upsert(conn: &PgConnection, user_form: &Form) -> Result<Self, Error>
125   where
126     Self: Sized;
127 }
128
129 pub trait MaybeOptional<T> {
130   fn get_optional(self) -> Option<T>;
131 }
132
133 impl<T> MaybeOptional<T> for T {
134   fn get_optional(self) -> Option<T> {
135     Some(self)
136   }
137 }
138
139 impl<T> MaybeOptional<T> for Option<T> {
140   fn get_optional(self) -> Option<T> {
141     self
142   }
143 }
144
145 pub trait ToSafe {
146   type SafeColumns;
147   fn safe_columns_tuple() -> Self::SafeColumns;
148 }
149
150 pub trait ToSafeSettings {
151   type SafeSettingsColumns;
152   fn safe_settings_columns_tuple() -> Self::SafeSettingsColumns;
153 }
154
155 pub trait ViewToVec {
156   type DbTuple;
157   fn from_tuple_to_vec(tuple: Vec<Self::DbTuple>) -> Vec<Self>
158   where
159     Self: Sized;
160 }
161
162 pub fn get_database_url_from_env() -> Result<String, VarError> {
163   env::var("LEMMY_DATABASE_URL")
164 }
165
166 #[derive(EnumString, ToString, Debug, Serialize, Deserialize, Clone, Copy)]
167 pub enum SortType {
168   Active,
169   Hot,
170   New,
171   TopDay,
172   TopWeek,
173   TopMonth,
174   TopYear,
175   TopAll,
176   MostComments,
177   NewComments,
178 }
179
180 #[derive(EnumString, ToString, Debug, Serialize, Deserialize, Clone, Copy)]
181 pub enum ListingType {
182   All,
183   Local,
184   Subscribed,
185   Community,
186 }
187
188 #[derive(EnumString, ToString, Debug, Serialize, Deserialize, Clone, Copy)]
189 pub enum SearchType {
190   All,
191   Comments,
192   Posts,
193   Communities,
194   Users,
195   Url,
196 }
197
198 pub fn from_opt_str_to_opt_enum<T: std::str::FromStr>(opt: &Option<String>) -> Option<T> {
199   opt.as_ref().map(|t| T::from_str(t).ok()).flatten()
200 }
201
202 pub fn fuzzy_search(q: &str) -> String {
203   let replaced = q.replace(" ", "%");
204   format!("%{}%", replaced)
205 }
206
207 pub fn limit_and_offset(page: Option<i64>, limit: Option<i64>) -> (i64, i64) {
208   let page = page.unwrap_or(1);
209   let limit = limit.unwrap_or(10);
210   let offset = limit * (page - 1);
211   (limit, offset)
212 }
213
214 pub fn is_email_regex(test: &str) -> bool {
215   EMAIL_REGEX.is_match(test)
216 }
217
218 pub fn diesel_option_overwrite(opt: &Option<String>) -> Option<Option<String>> {
219   match opt {
220     // An empty string is an erase
221     Some(unwrapped) => {
222       if !unwrapped.eq("") {
223         Some(Some(unwrapped.to_owned()))
224       } else {
225         Some(None)
226       }
227     }
228     None => None,
229   }
230 }
231
232 pub fn diesel_option_overwrite_to_url(
233   opt: &Option<String>,
234 ) -> Result<Option<Option<DbUrl>>, ApiError> {
235   match opt.as_ref().map(|s| s.as_str()) {
236     // An empty string is an erase
237     Some("") => Ok(Some(None)),
238     Some(str_url) => match Url::parse(str_url) {
239       Ok(url) => Ok(Some(Some(url.into()))),
240       Err(_) => Err(ApiError::err("invalid_url")),
241     },
242     None => Ok(None),
243   }
244 }
245
246 embed_migrations!();
247
248 pub fn establish_unpooled_connection() -> PgConnection {
249   let db_url = match get_database_url_from_env() {
250     Ok(url) => url,
251     Err(e) => panic!(
252       "Failed to read database URL from env var LEMMY_DATABASE_URL: {}",
253       e
254     ),
255   };
256   let conn =
257     PgConnection::establish(&db_url).unwrap_or_else(|_| panic!("Error connecting to {}", db_url));
258   embedded_migrations::run(&conn).expect("load migrations");
259   conn
260 }
261
262 lazy_static! {
263   static ref EMAIL_REGEX: Regex =
264     Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$")
265       .expect("compile email regex");
266 }
267
268 pub mod functions {
269   use diesel::sql_types::*;
270
271   sql_function! {
272     fn hot_rank(score: BigInt, time: Timestamp) -> Integer;
273   }
274 }
275
276 #[cfg(test)]
277 mod tests {
278   use super::{fuzzy_search, *};
279   use crate::is_email_regex;
280
281   #[test]
282   fn test_fuzzy_search() {
283     let test = "This is a fuzzy search";
284     assert_eq!(fuzzy_search(test), "%This%is%a%fuzzy%search%".to_string());
285   }
286
287   #[test]
288   fn test_email() {
289     assert!(is_email_regex("gush@gmail.com"));
290     assert!(!is_email_regex("nada_neutho"));
291   }
292
293   #[test]
294   fn test_diesel_option_overwrite() {
295     assert_eq!(diesel_option_overwrite(&None), None);
296     assert_eq!(diesel_option_overwrite(&Some("".to_string())), Some(None));
297     assert_eq!(
298       diesel_option_overwrite(&Some("test".to_string())),
299       Some(Some("test".to_string()))
300     );
301   }
302
303   #[test]
304   fn test_diesel_option_overwrite_to_url() {
305     assert!(matches!(diesel_option_overwrite_to_url(&None), Ok(None)));
306     assert!(matches!(
307       diesel_option_overwrite_to_url(&Some("".to_string())),
308       Ok(Some(None))
309     ));
310     assert!(matches!(
311       diesel_option_overwrite_to_url(&Some("invalid_url".to_string())),
312       Err(_)
313     ));
314     let example_url = "https://example.com";
315     assert!(matches!(
316       diesel_option_overwrite_to_url(&Some(example_url.to_string())),
317       Ok(Some(Some(url))) if url == Url::parse(example_url).unwrap().into()
318     ));
319   }
320 }