]> Untitled Git - lemmy.git/blob - crates/db_queries/src/lib.rs
Adding forum sort for post_aggregates. Fixes #1312 (#1400)
[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 use diesel::{result::Error, *};
13 use lemmy_db_schema::Url;
14 use regex::Regex;
15 use serde::{Deserialize, Serialize};
16 use std::{env, env::VarError};
17
18 pub mod aggregates;
19 pub mod source;
20
21 pub type DbPool = diesel::r2d2::Pool<diesel::r2d2::ConnectionManager<diesel::PgConnection>>;
22
23 pub trait Crud<T> {
24   fn create(conn: &PgConnection, form: &T) -> Result<Self, Error>
25   where
26     Self: Sized;
27   fn read(conn: &PgConnection, id: i32) -> Result<Self, Error>
28   where
29     Self: Sized;
30   fn update(conn: &PgConnection, id: i32, form: &T) -> Result<Self, Error>
31   where
32     Self: Sized;
33   fn delete(_conn: &PgConnection, _id: i32) -> Result<usize, Error>
34   where
35     Self: Sized,
36   {
37     unimplemented!()
38   }
39 }
40
41 pub trait Followable<T> {
42   fn follow(conn: &PgConnection, form: &T) -> Result<Self, Error>
43   where
44     Self: Sized;
45   fn follow_accepted(conn: &PgConnection, community_id: i32, user_id: i32) -> Result<Self, Error>
46   where
47     Self: Sized;
48   fn unfollow(conn: &PgConnection, form: &T) -> Result<usize, Error>
49   where
50     Self: Sized;
51   fn has_local_followers(conn: &PgConnection, community_id: i32) -> Result<bool, Error>;
52 }
53
54 pub trait Joinable<T> {
55   fn join(conn: &PgConnection, form: &T) -> Result<Self, Error>
56   where
57     Self: Sized;
58   fn leave(conn: &PgConnection, form: &T) -> Result<usize, Error>
59   where
60     Self: Sized;
61 }
62
63 pub trait Likeable<T> {
64   fn like(conn: &PgConnection, form: &T) -> Result<Self, Error>
65   where
66     Self: Sized;
67   fn remove(conn: &PgConnection, user_id: i32, item_id: i32) -> Result<usize, Error>
68   where
69     Self: Sized;
70 }
71
72 pub trait Bannable<T> {
73   fn ban(conn: &PgConnection, form: &T) -> Result<Self, Error>
74   where
75     Self: Sized;
76   fn unban(conn: &PgConnection, form: &T) -> Result<usize, Error>
77   where
78     Self: Sized;
79 }
80
81 pub trait Saveable<T> {
82   fn save(conn: &PgConnection, form: &T) -> Result<Self, Error>
83   where
84     Self: Sized;
85   fn unsave(conn: &PgConnection, form: &T) -> Result<usize, Error>
86   where
87     Self: Sized;
88 }
89
90 pub trait Readable<T> {
91   fn mark_as_read(conn: &PgConnection, form: &T) -> Result<Self, Error>
92   where
93     Self: Sized;
94   fn mark_as_unread(conn: &PgConnection, form: &T) -> Result<usize, Error>
95   where
96     Self: Sized;
97 }
98
99 pub trait Reportable<T> {
100   fn report(conn: &PgConnection, form: &T) -> Result<Self, Error>
101   where
102     Self: Sized;
103   fn resolve(conn: &PgConnection, report_id: i32, resolver_id: i32) -> Result<usize, Error>
104   where
105     Self: Sized;
106   fn unresolve(conn: &PgConnection, report_id: i32, resolver_id: i32) -> Result<usize, Error>
107   where
108     Self: Sized;
109 }
110
111 pub trait ApubObject<T> {
112   fn read_from_apub_id(conn: &PgConnection, object_id: &Url) -> Result<Self, Error>
113   where
114     Self: Sized;
115   fn upsert(conn: &PgConnection, user_form: &T) -> Result<Self, Error>
116   where
117     Self: Sized;
118 }
119
120 pub trait MaybeOptional<T> {
121   fn get_optional(self) -> Option<T>;
122 }
123
124 impl<T> MaybeOptional<T> for T {
125   fn get_optional(self) -> Option<T> {
126     Some(self)
127   }
128 }
129
130 impl<T> MaybeOptional<T> for Option<T> {
131   fn get_optional(self) -> Option<T> {
132     self
133   }
134 }
135
136 pub trait ToSafe {
137   type SafeColumns;
138   fn safe_columns_tuple() -> Self::SafeColumns;
139 }
140
141 pub trait ToSafeSettings {
142   type SafeSettingsColumns;
143   fn safe_settings_columns_tuple() -> Self::SafeSettingsColumns;
144 }
145
146 pub trait ViewToVec {
147   type DbTuple;
148   fn from_tuple_to_vec(tuple: Vec<Self::DbTuple>) -> Vec<Self>
149   where
150     Self: Sized;
151 }
152
153 pub fn get_database_url_from_env() -> Result<String, VarError> {
154   env::var("LEMMY_DATABASE_URL")
155 }
156
157 #[derive(EnumString, ToString, Debug, Serialize, Deserialize)]
158 pub enum SortType {
159   Active,
160   Hot,
161   New,
162   TopDay,
163   TopWeek,
164   TopMonth,
165   TopYear,
166   TopAll,
167   MostComments,
168 }
169
170 #[derive(EnumString, ToString, Debug, Serialize, Deserialize, Clone)]
171 pub enum ListingType {
172   All,
173   Local,
174   Subscribed,
175   Community,
176 }
177
178 #[derive(EnumString, ToString, Debug, Serialize, Deserialize)]
179 pub enum SearchType {
180   All,
181   Comments,
182   Posts,
183   Communities,
184   Users,
185   Url,
186 }
187
188 pub fn fuzzy_search(q: &str) -> String {
189   let replaced = q.replace(" ", "%");
190   format!("%{}%", replaced)
191 }
192
193 pub fn limit_and_offset(page: Option<i64>, limit: Option<i64>) -> (i64, i64) {
194   let page = page.unwrap_or(1);
195   let limit = limit.unwrap_or(10);
196   let offset = limit * (page - 1);
197   (limit, offset)
198 }
199
200 pub fn is_email_regex(test: &str) -> bool {
201   EMAIL_REGEX.is_match(test)
202 }
203
204 pub fn diesel_option_overwrite(opt: &Option<String>) -> Option<Option<String>> {
205   match opt {
206     // An empty string is an erase
207     Some(unwrapped) => {
208       if !unwrapped.eq("") {
209         Some(Some(unwrapped.to_owned()))
210       } else {
211         Some(None)
212       }
213     }
214     None => None,
215   }
216 }
217
218 embed_migrations!();
219
220 pub fn establish_unpooled_connection() -> PgConnection {
221   let db_url = match get_database_url_from_env() {
222     Ok(url) => url,
223     Err(e) => panic!(
224       "Failed to read database URL from env var LEMMY_DATABASE_URL: {}",
225       e
226     ),
227   };
228   let conn =
229     PgConnection::establish(&db_url).unwrap_or_else(|_| panic!("Error connecting to {}", db_url));
230   embedded_migrations::run(&conn).unwrap();
231   conn
232 }
233
234 lazy_static! {
235   static ref EMAIL_REGEX: Regex =
236     Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap();
237 }
238
239 pub mod functions {
240   use diesel::sql_types::*;
241
242   sql_function! {
243     fn hot_rank(score: BigInt, time: Timestamp) -> Integer;
244   }
245 }
246
247 #[cfg(test)]
248 mod tests {
249   use super::fuzzy_search;
250   use crate::is_email_regex;
251
252   #[test]
253   fn test_fuzzy_search() {
254     let test = "This is a fuzzy search";
255     assert_eq!(fuzzy_search(test), "%This%is%a%fuzzy%search%".to_string());
256   }
257
258   #[test]
259   fn test_email() {
260     assert!(is_email_regex("gush@gmail.com"));
261     assert!(!is_email_regex("nada_neutho"));
262   }
263 }