]> Untitled Git - lemmy.git/blob - crates/db_queries/src/lib.rs
Use Url type for ap_id fields in database (fixes #1364)
[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 }
168
169 #[derive(EnumString, ToString, Debug, Serialize, Deserialize, Clone)]
170 pub enum ListingType {
171   All,
172   Local,
173   Subscribed,
174   Community,
175 }
176
177 #[derive(EnumString, ToString, Debug, Serialize, Deserialize)]
178 pub enum SearchType {
179   All,
180   Comments,
181   Posts,
182   Communities,
183   Users,
184   Url,
185 }
186
187 pub fn fuzzy_search(q: &str) -> String {
188   let replaced = q.replace(" ", "%");
189   format!("%{}%", replaced)
190 }
191
192 pub fn limit_and_offset(page: Option<i64>, limit: Option<i64>) -> (i64, i64) {
193   let page = page.unwrap_or(1);
194   let limit = limit.unwrap_or(10);
195   let offset = limit * (page - 1);
196   (limit, offset)
197 }
198
199 pub fn is_email_regex(test: &str) -> bool {
200   EMAIL_REGEX.is_match(test)
201 }
202
203 pub fn diesel_option_overwrite(opt: &Option<String>) -> Option<Option<String>> {
204   match opt {
205     // An empty string is an erase
206     Some(unwrapped) => {
207       if !unwrapped.eq("") {
208         Some(Some(unwrapped.to_owned()))
209       } else {
210         Some(None)
211       }
212     }
213     None => None,
214   }
215 }
216
217 embed_migrations!();
218
219 pub fn establish_unpooled_connection() -> PgConnection {
220   let db_url = match get_database_url_from_env() {
221     Ok(url) => url,
222     Err(e) => panic!(
223       "Failed to read database URL from env var LEMMY_DATABASE_URL: {}",
224       e
225     ),
226   };
227   let conn =
228     PgConnection::establish(&db_url).unwrap_or_else(|_| panic!("Error connecting to {}", db_url));
229   embedded_migrations::run(&conn).unwrap();
230   conn
231 }
232
233 lazy_static! {
234   static ref EMAIL_REGEX: Regex =
235     Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap();
236 }
237
238 pub mod functions {
239   use diesel::sql_types::*;
240
241   sql_function! {
242     fn hot_rank(score: BigInt, time: Timestamp) -> Integer;
243   }
244 }
245
246 #[cfg(test)]
247 mod tests {
248   use super::fuzzy_search;
249   use crate::is_email_regex;
250
251   #[test]
252   fn test_fuzzy_search() {
253     let test = "This is a fuzzy search";
254     assert_eq!(fuzzy_search(test), "%This%is%a%fuzzy%search%".to_string());
255   }
256
257   #[test]
258   fn test_email() {
259     assert!(is_email_regex("gush@gmail.com"));
260     assert!(!is_email_regex("nada_neutho"));
261   }
262 }