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