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