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