]> Untitled Git - lemmy.git/blob - lemmy_db/src/lib.rs
Merge remote-tracking branch 'origin/split-db-workspace' into move_views_to_diesel_split
[lemmy.git] / lemmy_db / 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 regex::Regex;
14 use serde::{Deserialize, Serialize};
15 use std::{env, env::VarError};
16
17 pub mod aggregates;
18 pub mod source;
19 pub mod views;
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: &str) -> 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(crate) trait ToSafe {
137   type SafeColumns;
138   fn safe_columns_tuple() -> Self::SafeColumns;
139 }
140
141 pub fn get_database_url_from_env() -> Result<String, VarError> {
142   env::var("LEMMY_DATABASE_URL")
143 }
144
145 #[derive(EnumString, ToString, Debug, Serialize, Deserialize)]
146 pub enum SortType {
147   Active,
148   Hot,
149   New,
150   TopDay,
151   TopWeek,
152   TopMonth,
153   TopYear,
154   TopAll,
155 }
156
157 #[derive(EnumString, ToString, Debug, Serialize, Deserialize, Clone)]
158 pub enum ListingType {
159   All,
160   Local,
161   Subscribed,
162   Community,
163 }
164
165 #[derive(EnumString, ToString, Debug, Serialize, Deserialize)]
166 pub enum SearchType {
167   All,
168   Comments,
169   Posts,
170   Communities,
171   Users,
172   Url,
173 }
174
175 pub fn fuzzy_search(q: &str) -> String {
176   let replaced = q.replace(" ", "%");
177   format!("%{}%", replaced)
178 }
179
180 pub fn limit_and_offset(page: Option<i64>, limit: Option<i64>) -> (i64, i64) {
181   let page = page.unwrap_or(1);
182   let limit = limit.unwrap_or(10);
183   let offset = limit * (page - 1);
184   (limit, offset)
185 }
186
187 pub fn is_email_regex(test: &str) -> bool {
188   EMAIL_REGEX.is_match(test)
189 }
190
191 pub fn diesel_option_overwrite(opt: &Option<String>) -> Option<Option<String>> {
192   match opt {
193     // An empty string is an erase
194     Some(unwrapped) => {
195       if !unwrapped.eq("") {
196         Some(Some(unwrapped.to_owned()))
197       } else {
198         Some(None)
199       }
200     }
201     None => None,
202   }
203 }
204
205 lazy_static! {
206   static ref EMAIL_REGEX: Regex =
207     Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap();
208 }
209
210 pub(crate) mod functions {
211   use diesel::sql_types::*;
212
213   sql_function! {
214     fn hot_rank(score: BigInt, time: Timestamp) -> Integer;
215   }
216 }
217
218 #[cfg(test)]
219 mod tests {
220   use super::fuzzy_search;
221   use crate::{get_database_url_from_env, is_email_regex};
222   use diesel::{Connection, PgConnection};
223
224   embed_migrations!();
225
226   pub fn establish_unpooled_connection() -> PgConnection {
227     let db_url = match get_database_url_from_env() {
228       Ok(url) => url,
229       Err(e) => panic!(
230         "Failed to read database URL from env var LEMMY_DATABASE_URL: {}",
231         e
232       ),
233     };
234     let conn =
235       PgConnection::establish(&db_url).unwrap_or_else(|_| panic!("Error connecting to {}", db_url));
236     embedded_migrations::run(&conn).unwrap();
237     conn
238   }
239
240   #[test]
241   fn test_fuzzy_search() {
242     let test = "This is a fuzzy search";
243     assert_eq!(fuzzy_search(test), "%This%is%a%fuzzy%search%".to_string());
244   }
245
246   #[test]
247   fn test_email() {
248     assert!(is_email_regex("gush@gmail.com"));
249     assert!(!is_email_regex("nada_neutho"));
250   }
251 }