]> Untitled Git - lemmy.git/blob - server/src/api/site.rs
Merge branch 'admin_settings' into dev
[lemmy.git] / server / src / api / site.rs
1 use super::*;
2 use crate::api::user::Register;
3 use crate::api::{Oper, Perform};
4 use crate::settings::Settings;
5 use diesel::PgConnection;
6 use log::info;
7 use std::str::FromStr;
8
9 #[derive(Serialize, Deserialize)]
10 pub struct ListCategories {}
11
12 #[derive(Serialize, Deserialize)]
13 pub struct ListCategoriesResponse {
14   categories: Vec<Category>,
15 }
16
17 #[derive(Serialize, Deserialize)]
18 pub struct Search {
19   q: String,
20   type_: String,
21   community_id: Option<i32>,
22   sort: String,
23   page: Option<i64>,
24   limit: Option<i64>,
25   auth: Option<String>,
26 }
27
28 #[derive(Serialize, Deserialize)]
29 pub struct SearchResponse {
30   type_: String,
31   comments: Vec<CommentView>,
32   posts: Vec<PostView>,
33   communities: Vec<CommunityView>,
34   users: Vec<UserView>,
35 }
36
37 #[derive(Serialize, Deserialize)]
38 pub struct GetModlog {
39   mod_user_id: Option<i32>,
40   community_id: Option<i32>,
41   page: Option<i64>,
42   limit: Option<i64>,
43 }
44
45 #[derive(Serialize, Deserialize)]
46 pub struct GetModlogResponse {
47   removed_posts: Vec<ModRemovePostView>,
48   locked_posts: Vec<ModLockPostView>,
49   stickied_posts: Vec<ModStickyPostView>,
50   removed_comments: Vec<ModRemoveCommentView>,
51   removed_communities: Vec<ModRemoveCommunityView>,
52   banned_from_community: Vec<ModBanFromCommunityView>,
53   banned: Vec<ModBanView>,
54   added_to_community: Vec<ModAddCommunityView>,
55   added: Vec<ModAddView>,
56 }
57
58 #[derive(Serialize, Deserialize)]
59 pub struct CreateSite {
60   pub name: String,
61   pub description: Option<String>,
62   pub enable_downvotes: bool,
63   pub open_registration: bool,
64   pub enable_nsfw: bool,
65   pub auth: String,
66 }
67
68 #[derive(Serialize, Deserialize)]
69 pub struct EditSite {
70   name: String,
71   description: Option<String>,
72   enable_downvotes: bool,
73   open_registration: bool,
74   enable_nsfw: bool,
75   auth: String,
76 }
77
78 #[derive(Serialize, Deserialize)]
79 pub struct GetSite {}
80
81 #[derive(Serialize, Deserialize)]
82 pub struct SiteResponse {
83   site: SiteView,
84 }
85
86 #[derive(Serialize, Deserialize)]
87 pub struct GetSiteResponse {
88   site: Option<SiteView>,
89   admins: Vec<UserView>,
90   banned: Vec<UserView>,
91   pub online: usize,
92 }
93
94 #[derive(Serialize, Deserialize)]
95 pub struct TransferSite {
96   user_id: i32,
97   auth: String,
98 }
99
100 #[derive(Serialize, Deserialize)]
101 pub struct GetSiteConfig {
102   auth: String,
103 }
104
105 #[derive(Serialize, Deserialize)]
106 pub struct GetSiteConfigResponse {
107   config_hjson: String,
108 }
109
110 #[derive(Serialize, Deserialize)]
111 pub struct SaveSiteConfig {
112   config_hjson: String,
113   auth: String,
114 }
115
116 impl Perform<ListCategoriesResponse> for Oper<ListCategories> {
117   fn perform(&self, conn: &PgConnection) -> Result<ListCategoriesResponse, Error> {
118     let _data: &ListCategories = &self.data;
119
120     let categories: Vec<Category> = Category::list_all(&conn)?;
121
122     // Return the jwt
123     Ok(ListCategoriesResponse { categories })
124   }
125 }
126
127 impl Perform<GetModlogResponse> for Oper<GetModlog> {
128   fn perform(&self, conn: &PgConnection) -> Result<GetModlogResponse, Error> {
129     let data: &GetModlog = &self.data;
130
131     let removed_posts = ModRemovePostView::list(
132       &conn,
133       data.community_id,
134       data.mod_user_id,
135       data.page,
136       data.limit,
137     )?;
138     let locked_posts = ModLockPostView::list(
139       &conn,
140       data.community_id,
141       data.mod_user_id,
142       data.page,
143       data.limit,
144     )?;
145     let stickied_posts = ModStickyPostView::list(
146       &conn,
147       data.community_id,
148       data.mod_user_id,
149       data.page,
150       data.limit,
151     )?;
152     let removed_comments = ModRemoveCommentView::list(
153       &conn,
154       data.community_id,
155       data.mod_user_id,
156       data.page,
157       data.limit,
158     )?;
159     let banned_from_community = ModBanFromCommunityView::list(
160       &conn,
161       data.community_id,
162       data.mod_user_id,
163       data.page,
164       data.limit,
165     )?;
166     let added_to_community = ModAddCommunityView::list(
167       &conn,
168       data.community_id,
169       data.mod_user_id,
170       data.page,
171       data.limit,
172     )?;
173
174     // These arrays are only for the full modlog, when a community isn't given
175     let (removed_communities, banned, added) = if data.community_id.is_none() {
176       (
177         ModRemoveCommunityView::list(&conn, data.mod_user_id, data.page, data.limit)?,
178         ModBanView::list(&conn, data.mod_user_id, data.page, data.limit)?,
179         ModAddView::list(&conn, data.mod_user_id, data.page, data.limit)?,
180       )
181     } else {
182       (Vec::new(), Vec::new(), Vec::new())
183     };
184
185     // Return the jwt
186     Ok(GetModlogResponse {
187       removed_posts,
188       locked_posts,
189       stickied_posts,
190       removed_comments,
191       removed_communities,
192       banned_from_community,
193       banned,
194       added_to_community,
195       added,
196     })
197   }
198 }
199
200 impl Perform<SiteResponse> for Oper<CreateSite> {
201   fn perform(&self, conn: &PgConnection) -> Result<SiteResponse, Error> {
202     let data: &CreateSite = &self.data;
203
204     let claims = match Claims::decode(&data.auth) {
205       Ok(claims) => claims.claims,
206       Err(_e) => return Err(APIError::err("not_logged_in").into()),
207     };
208
209     if let Err(slurs) = slur_check(&data.name) {
210       return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
211     }
212
213     if let Some(description) = &data.description {
214       if let Err(slurs) = slur_check(description) {
215         return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
216       }
217     }
218
219     let user_id = claims.id;
220
221     // Make sure user is an admin
222     if !UserView::read(&conn, user_id)?.admin {
223       return Err(APIError::err("not_an_admin").into());
224     }
225
226     let site_form = SiteForm {
227       name: data.name.to_owned(),
228       description: data.description.to_owned(),
229       creator_id: user_id,
230       enable_downvotes: data.enable_downvotes,
231       open_registration: data.open_registration,
232       enable_nsfw: data.enable_nsfw,
233       updated: None,
234     };
235
236     match Site::create(&conn, &site_form) {
237       Ok(site) => site,
238       Err(_e) => return Err(APIError::err("site_already_exists").into()),
239     };
240
241     let site_view = SiteView::read(&conn)?;
242
243     Ok(SiteResponse { site: site_view })
244   }
245 }
246
247 impl Perform<SiteResponse> for Oper<EditSite> {
248   fn perform(&self, conn: &PgConnection) -> Result<SiteResponse, Error> {
249     let data: &EditSite = &self.data;
250
251     let claims = match Claims::decode(&data.auth) {
252       Ok(claims) => claims.claims,
253       Err(_e) => return Err(APIError::err("not_logged_in").into()),
254     };
255
256     if let Err(slurs) = slur_check(&data.name) {
257       return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
258     }
259
260     if let Some(description) = &data.description {
261       if let Err(slurs) = slur_check(description) {
262         return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
263       }
264     }
265
266     let user_id = claims.id;
267
268     // Make sure user is an admin
269     if !UserView::read(&conn, user_id)?.admin {
270       return Err(APIError::err("not_an_admin").into());
271     }
272
273     let found_site = Site::read(&conn, 1)?;
274
275     let site_form = SiteForm {
276       name: data.name.to_owned(),
277       description: data.description.to_owned(),
278       creator_id: found_site.creator_id,
279       updated: Some(naive_now()),
280       enable_downvotes: data.enable_downvotes,
281       open_registration: data.open_registration,
282       enable_nsfw: data.enable_nsfw,
283     };
284
285     match Site::update(&conn, 1, &site_form) {
286       Ok(site) => site,
287       Err(_e) => return Err(APIError::err("couldnt_update_site").into()),
288     };
289
290     let site_view = SiteView::read(&conn)?;
291
292     Ok(SiteResponse { site: site_view })
293   }
294 }
295
296 impl Perform<GetSiteResponse> for Oper<GetSite> {
297   fn perform(&self, conn: &PgConnection) -> Result<GetSiteResponse, Error> {
298     let _data: &GetSite = &self.data;
299
300     let site = Site::read(&conn, 1);
301     let site_view = if site.is_ok() {
302       Some(SiteView::read(&conn)?)
303     } else if let Some(setup) = Settings::get().setup.as_ref() {
304       let register = Register {
305         username: setup.admin_username.to_owned(),
306         email: setup.admin_email.to_owned(),
307         password: setup.admin_password.to_owned(),
308         password_verify: setup.admin_password.to_owned(),
309         admin: true,
310         show_nsfw: true,
311       };
312       let login_response = Oper::new(register).perform(&conn)?;
313       info!("Admin {} created", setup.admin_username);
314
315       let create_site = CreateSite {
316         name: setup.site_name.to_owned(),
317         description: None,
318         enable_downvotes: false,
319         open_registration: false,
320         enable_nsfw: false,
321         auth: login_response.jwt,
322       };
323       Oper::new(create_site).perform(&conn)?;
324       info!("Site {} created", setup.site_name);
325       Some(SiteView::read(&conn)?)
326     } else {
327       None
328     };
329
330     let mut admins = UserView::admins(&conn)?;
331     if site_view.is_some() {
332       let site_creator_id = site_view.to_owned().unwrap().creator_id;
333       let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
334       let creator_user = admins.remove(creator_index);
335       admins.insert(0, creator_user);
336     }
337
338     let banned = UserView::banned(&conn)?;
339
340     Ok(GetSiteResponse {
341       site: site_view,
342       admins,
343       banned,
344       online: 0,
345     })
346   }
347 }
348
349 impl Perform<SearchResponse> for Oper<Search> {
350   fn perform(&self, conn: &PgConnection) -> Result<SearchResponse, Error> {
351     let data: &Search = &self.data;
352
353     let user_id: Option<i32> = match &data.auth {
354       Some(auth) => match Claims::decode(&auth) {
355         Ok(claims) => {
356           let user_id = claims.claims.id;
357           Some(user_id)
358         }
359         Err(_e) => None,
360       },
361       None => None,
362     };
363
364     let sort = SortType::from_str(&data.sort)?;
365     let type_ = SearchType::from_str(&data.type_)?;
366
367     let mut posts = Vec::new();
368     let mut comments = Vec::new();
369     let mut communities = Vec::new();
370     let mut users = Vec::new();
371
372     // TODO no clean / non-nsfw searching rn
373
374     match type_ {
375       SearchType::Posts => {
376         posts = PostQueryBuilder::create(&conn)
377           .sort(&sort)
378           .show_nsfw(true)
379           .for_community_id(data.community_id)
380           .search_term(data.q.to_owned())
381           .my_user_id(user_id)
382           .page(data.page)
383           .limit(data.limit)
384           .list()?;
385       }
386       SearchType::Comments => {
387         comments = CommentQueryBuilder::create(&conn)
388           .sort(&sort)
389           .search_term(data.q.to_owned())
390           .my_user_id(user_id)
391           .page(data.page)
392           .limit(data.limit)
393           .list()?;
394       }
395       SearchType::Communities => {
396         communities = CommunityQueryBuilder::create(&conn)
397           .sort(&sort)
398           .search_term(data.q.to_owned())
399           .page(data.page)
400           .limit(data.limit)
401           .list()?;
402       }
403       SearchType::Users => {
404         users = UserQueryBuilder::create(&conn)
405           .sort(&sort)
406           .search_term(data.q.to_owned())
407           .page(data.page)
408           .limit(data.limit)
409           .list()?;
410       }
411       SearchType::All => {
412         posts = PostQueryBuilder::create(&conn)
413           .sort(&sort)
414           .show_nsfw(true)
415           .for_community_id(data.community_id)
416           .search_term(data.q.to_owned())
417           .my_user_id(user_id)
418           .page(data.page)
419           .limit(data.limit)
420           .list()?;
421
422         comments = CommentQueryBuilder::create(&conn)
423           .sort(&sort)
424           .search_term(data.q.to_owned())
425           .my_user_id(user_id)
426           .page(data.page)
427           .limit(data.limit)
428           .list()?;
429
430         communities = CommunityQueryBuilder::create(&conn)
431           .sort(&sort)
432           .search_term(data.q.to_owned())
433           .page(data.page)
434           .limit(data.limit)
435           .list()?;
436
437         users = UserQueryBuilder::create(&conn)
438           .sort(&sort)
439           .search_term(data.q.to_owned())
440           .page(data.page)
441           .limit(data.limit)
442           .list()?;
443       }
444       SearchType::Url => {
445         posts = PostQueryBuilder::create(&conn)
446           .sort(&sort)
447           .show_nsfw(true)
448           .for_community_id(data.community_id)
449           .url_search(data.q.to_owned())
450           .page(data.page)
451           .limit(data.limit)
452           .list()?;
453       }
454     };
455
456     // Return the jwt
457     Ok(SearchResponse {
458       type_: data.type_.to_owned(),
459       comments,
460       posts,
461       communities,
462       users,
463     })
464   }
465 }
466
467 impl Perform<GetSiteResponse> for Oper<TransferSite> {
468   fn perform(&self, conn: &PgConnection) -> Result<GetSiteResponse, Error> {
469     let data: &TransferSite = &self.data;
470
471     let claims = match Claims::decode(&data.auth) {
472       Ok(claims) => claims.claims,
473       Err(_e) => return Err(APIError::err("not_logged_in").into()),
474     };
475
476     let user_id = claims.id;
477
478     let read_site = Site::read(&conn, 1)?;
479
480     // Make sure user is the creator
481     if read_site.creator_id != user_id {
482       return Err(APIError::err("not_an_admin").into());
483     }
484
485     let site_form = SiteForm {
486       name: read_site.name,
487       description: read_site.description,
488       creator_id: data.user_id,
489       updated: Some(naive_now()),
490       enable_downvotes: read_site.enable_downvotes,
491       open_registration: read_site.open_registration,
492       enable_nsfw: read_site.enable_nsfw,
493     };
494
495     match Site::update(&conn, 1, &site_form) {
496       Ok(site) => site,
497       Err(_e) => return Err(APIError::err("couldnt_update_site").into()),
498     };
499
500     // Mod tables
501     let form = ModAddForm {
502       mod_user_id: user_id,
503       other_user_id: data.user_id,
504       removed: Some(false),
505     };
506
507     ModAdd::create(&conn, &form)?;
508
509     let site_view = SiteView::read(&conn)?;
510
511     let mut admins = UserView::admins(&conn)?;
512     let creator_index = admins
513       .iter()
514       .position(|r| r.id == site_view.creator_id)
515       .unwrap();
516     let creator_user = admins.remove(creator_index);
517     admins.insert(0, creator_user);
518
519     let banned = UserView::banned(&conn)?;
520
521     Ok(GetSiteResponse {
522       site: Some(site_view),
523       admins,
524       banned,
525       online: 0,
526     })
527   }
528 }
529
530 impl Perform<GetSiteConfigResponse> for Oper<GetSiteConfig> {
531   fn perform(&self, conn: &PgConnection) -> Result<GetSiteConfigResponse, Error> {
532     let data: &GetSiteConfig = &self.data;
533
534     let claims = match Claims::decode(&data.auth) {
535       Ok(claims) => claims.claims,
536       Err(_e) => return Err(APIError::err("not_logged_in").into()),
537     };
538
539     let user_id = claims.id;
540
541     // Only let admins read this
542     let admins = UserView::admins(&conn)?;
543     let admin_ids: Vec<i32> = admins.into_iter().map(|m| m.id).collect();
544
545     if !admin_ids.contains(&user_id) {
546       return Err(APIError::err("not_an_admin").into());
547     }
548
549     let config_hjson = Settings::read_config_file()?;
550
551     Ok(GetSiteConfigResponse { config_hjson })
552   }
553 }
554
555 impl Perform<GetSiteConfigResponse> for Oper<SaveSiteConfig> {
556   fn perform(&self, conn: &PgConnection) -> Result<GetSiteConfigResponse, Error> {
557     let data: &SaveSiteConfig = &self.data;
558
559     let claims = match Claims::decode(&data.auth) {
560       Ok(claims) => claims.claims,
561       Err(_e) => return Err(APIError::err("not_logged_in").into()),
562     };
563
564     let user_id = claims.id;
565
566     // Only let admins read this
567     let admins = UserView::admins(&conn)?;
568     let admin_ids: Vec<i32> = admins.into_iter().map(|m| m.id).collect();
569
570     if !admin_ids.contains(&user_id) {
571       return Err(APIError::err("not_an_admin").into());
572     }
573
574     // Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem
575     let config_hjson = match Settings::save_config_file(&data.config_hjson) {
576       Ok(config_hjson) => config_hjson,
577       Err(_e) => return Err(APIError::err("couldnt_update_site").into()),
578     };
579
580     Ok(GetSiteConfigResponse { config_hjson })
581   }
582 }