2 api::{APIError, Oper, Perform},
4 extensions::signatures::generate_actor_keypair,
27 server::{JoinCommunityRoom, SendCommunityRoomMessage},
33 r2d2::{ConnectionManager, Pool},
37 use serde::{Deserialize, Serialize};
38 use std::str::FromStr;
40 #[derive(Serialize, Deserialize)]
41 pub struct GetCommunity {
43 pub name: Option<String>,
47 #[derive(Serialize, Deserialize)]
48 pub struct GetCommunityResponse {
49 pub community: CommunityView,
50 pub moderators: Vec<CommunityModeratorView>,
51 pub admins: Vec<UserView>,
55 #[derive(Serialize, Deserialize)]
56 pub struct CreateCommunity {
59 description: Option<String>,
65 #[derive(Serialize, Deserialize, Clone)]
66 pub struct CommunityResponse {
67 pub community: CommunityView,
70 #[derive(Serialize, Deserialize, Debug)]
71 pub struct ListCommunities {
73 pub page: Option<i64>,
74 pub limit: Option<i64>,
75 pub auth: Option<String>,
78 #[derive(Serialize, Deserialize, Debug)]
79 pub struct ListCommunitiesResponse {
80 pub communities: Vec<CommunityView>,
83 #[derive(Serialize, Deserialize, Clone)]
84 pub struct BanFromCommunity {
85 pub community_id: i32,
88 reason: Option<String>,
93 #[derive(Serialize, Deserialize, Clone)]
94 pub struct BanFromCommunityResponse {
99 #[derive(Serialize, Deserialize)]
100 pub struct AddModToCommunity {
101 pub community_id: i32,
107 #[derive(Serialize, Deserialize, Clone)]
108 pub struct AddModToCommunityResponse {
109 moderators: Vec<CommunityModeratorView>,
112 #[derive(Serialize, Deserialize)]
113 pub struct EditCommunity {
117 description: Option<String>,
119 removed: Option<bool>,
120 deleted: Option<bool>,
122 reason: Option<String>,
123 expires: Option<i64>,
127 #[derive(Serialize, Deserialize)]
128 pub struct FollowCommunity {
134 #[derive(Serialize, Deserialize)]
135 pub struct GetFollowedCommunities {
139 #[derive(Serialize, Deserialize)]
140 pub struct GetFollowedCommunitiesResponse {
141 communities: Vec<CommunityFollowerView>,
144 #[derive(Serialize, Deserialize)]
145 pub struct TransferCommunity {
151 impl Perform for Oper<GetCommunity> {
152 type Response = GetCommunityResponse;
156 pool: Pool<ConnectionManager<PgConnection>>,
157 websocket_info: Option<WebsocketInfo>,
158 ) -> Result<GetCommunityResponse, Error> {
159 let data: &GetCommunity = &self.data;
161 let user_id: Option<i32> = match &data.auth {
162 Some(auth) => match Claims::decode(&auth) {
164 let user_id = claims.claims.id;
172 let conn = pool.get()?;
174 let community = match data.id {
175 Some(id) => Community::read(&conn, id)?,
177 match Community::read_from_name(
179 &data.name.to_owned().unwrap_or_else(|| "main".to_string()),
181 Ok(community) => community,
182 Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
187 let community_view = match CommunityView::read(&conn, community.id, user_id) {
188 Ok(community) => community,
189 Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
192 let moderators = match CommunityModeratorView::for_community(&conn, community.id) {
193 Ok(moderators) => moderators,
194 Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
197 let site_creator_id = Site::read(&conn, 1)?.creator_id;
198 let mut admins = UserView::admins(&conn)?;
199 let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
200 let creator_user = admins.remove(creator_index);
201 admins.insert(0, creator_user);
203 let online = if let Some(ws) = websocket_info {
204 if let Some(id) = ws.id {
205 ws.chatserver.do_send(JoinCommunityRoom {
206 community_id: community.id,
214 // ws.chatserver.send(GetCommunityUsersOnline {community_id}).await.unwrap()
216 // Runtime::new().unwrap().block_on(fut)
221 let res = GetCommunityResponse {
222 community: community_view,
233 impl Perform for Oper<CreateCommunity> {
234 type Response = CommunityResponse;
238 pool: Pool<ConnectionManager<PgConnection>>,
239 _websocket_info: Option<WebsocketInfo>,
240 ) -> Result<CommunityResponse, Error> {
241 let data: &CreateCommunity = &self.data;
243 let claims = match Claims::decode(&data.auth) {
244 Ok(claims) => claims.claims,
245 Err(_e) => return Err(APIError::err("not_logged_in").into()),
248 if let Err(slurs) = slur_check(&data.name) {
249 return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
252 if let Err(slurs) = slur_check(&data.title) {
253 return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
256 if let Some(description) = &data.description {
257 if let Err(slurs) = slur_check(description) {
258 return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
262 let user_id = claims.id;
264 let conn = pool.get()?;
266 // Check for a site ban
267 if UserView::read(&conn, user_id)?.banned {
268 return Err(APIError::err("site_ban").into());
271 // When you create a community, make sure the user becomes a moderator and a follower
272 let keypair = generate_actor_keypair()?;
274 let community_form = CommunityForm {
275 name: data.name.to_owned(),
276 title: data.title.to_owned(),
277 description: data.description.to_owned(),
278 category_id: data.category_id,
284 actor_id: make_apub_endpoint(EndpointType::Community, &data.name).to_string(),
286 private_key: Some(keypair.private_key),
287 public_key: Some(keypair.public_key),
288 last_refreshed_at: None,
292 let inserted_community = match Community::create(&conn, &community_form) {
293 Ok(community) => community,
294 Err(_e) => return Err(APIError::err("community_already_exists").into()),
297 let community_moderator_form = CommunityModeratorForm {
298 community_id: inserted_community.id,
302 let _inserted_community_moderator =
303 match CommunityModerator::join(&conn, &community_moderator_form) {
305 Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
308 let community_follower_form = CommunityFollowerForm {
309 community_id: inserted_community.id,
313 let _inserted_community_follower =
314 match CommunityFollower::follow(&conn, &community_follower_form) {
316 Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
319 let community_view = CommunityView::read(&conn, inserted_community.id, Some(user_id))?;
321 Ok(CommunityResponse {
322 community: community_view,
327 impl Perform for Oper<EditCommunity> {
328 type Response = CommunityResponse;
332 pool: Pool<ConnectionManager<PgConnection>>,
333 websocket_info: Option<WebsocketInfo>,
334 ) -> Result<CommunityResponse, Error> {
335 let data: &EditCommunity = &self.data;
337 if let Err(slurs) = slur_check(&data.name) {
338 return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
341 if let Err(slurs) = slur_check(&data.title) {
342 return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
345 if let Some(description) = &data.description {
346 if let Err(slurs) = slur_check(description) {
347 return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
351 let claims = match Claims::decode(&data.auth) {
352 Ok(claims) => claims.claims,
353 Err(_e) => return Err(APIError::err("not_logged_in").into()),
356 let user_id = claims.id;
358 let conn = pool.get()?;
360 // Check for a site ban
361 let user = User_::read(&conn, user_id)?;
363 return Err(APIError::err("site_ban").into());
367 let mut editors: Vec<i32> = Vec::new();
369 &mut CommunityModeratorView::for_community(&conn, data.edit_id)?
374 editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect());
375 if !editors.contains(&user_id) {
376 return Err(APIError::err("no_community_edit_allowed").into());
379 let read_community = Community::read(&conn, data.edit_id)?;
381 let community_form = CommunityForm {
382 name: data.name.to_owned(),
383 title: data.title.to_owned(),
384 description: data.description.to_owned(),
385 category_id: data.category_id.to_owned(),
387 removed: data.removed.to_owned(),
388 deleted: data.deleted.to_owned(),
390 updated: Some(naive_now()),
391 actor_id: read_community.actor_id,
392 local: read_community.local,
393 private_key: read_community.private_key,
394 public_key: read_community.public_key,
395 last_refreshed_at: None,
399 let updated_community = match Community::update(&conn, data.edit_id, &community_form) {
400 Ok(community) => community,
401 Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
405 if let Some(removed) = data.removed.to_owned() {
406 let expires = match data.expires {
407 Some(time) => Some(naive_from_unix(time)),
410 let form = ModRemoveCommunityForm {
411 mod_user_id: user_id,
412 community_id: data.edit_id,
413 removed: Some(removed),
414 reason: data.reason.to_owned(),
417 ModRemoveCommunity::create(&conn, &form)?;
420 if let Some(deleted) = data.deleted.to_owned() {
422 updated_community.send_delete(&user, &conn)?;
424 updated_community.send_undo_delete(&user, &conn)?;
426 } else if let Some(removed) = data.removed.to_owned() {
428 updated_community.send_remove(&user, &conn)?;
430 updated_community.send_undo_remove(&user, &conn)?;
434 let community_view = CommunityView::read(&conn, data.edit_id, Some(user_id))?;
436 let res = CommunityResponse {
437 community: community_view,
440 if let Some(ws) = websocket_info {
441 // Strip out the user id and subscribed when sending to others
442 let mut res_sent = res.clone();
443 res_sent.community.user_id = None;
444 res_sent.community.subscribed = None;
446 ws.chatserver.do_send(SendCommunityRoomMessage {
447 op: UserOperation::EditCommunity,
449 community_id: data.edit_id,
458 impl Perform for Oper<ListCommunities> {
459 type Response = ListCommunitiesResponse;
463 pool: Pool<ConnectionManager<PgConnection>>,
464 _websocket_info: Option<WebsocketInfo>,
465 ) -> Result<ListCommunitiesResponse, Error> {
466 let data: &ListCommunities = &self.data;
468 let user_claims: Option<Claims> = match &data.auth {
469 Some(auth) => match Claims::decode(&auth) {
470 Ok(claims) => Some(claims.claims),
476 let user_id = match &user_claims {
477 Some(claims) => Some(claims.id),
481 let show_nsfw = match &user_claims {
482 Some(claims) => claims.show_nsfw,
486 let sort = SortType::from_str(&data.sort)?;
488 let conn = pool.get()?;
490 let communities = CommunityQueryBuilder::create(&conn)
493 .show_nsfw(show_nsfw)
499 Ok(ListCommunitiesResponse { communities })
503 impl Perform for Oper<FollowCommunity> {
504 type Response = CommunityResponse;
508 pool: Pool<ConnectionManager<PgConnection>>,
509 _websocket_info: Option<WebsocketInfo>,
510 ) -> Result<CommunityResponse, Error> {
511 let data: &FollowCommunity = &self.data;
513 let claims = match Claims::decode(&data.auth) {
514 Ok(claims) => claims.claims,
515 Err(_e) => return Err(APIError::err("not_logged_in").into()),
518 let user_id = claims.id;
520 let conn = pool.get()?;
522 let community = Community::read(&conn, data.community_id)?;
523 let community_follower_form = CommunityFollowerForm {
524 community_id: data.community_id,
530 match CommunityFollower::follow(&conn, &community_follower_form) {
532 Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
535 match CommunityFollower::unfollow(&conn, &community_follower_form) {
537 Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
541 let user = User_::read(&conn, user_id)?;
544 // Dont actually add to the community followers here, because you need
545 // to wait for the accept
546 user.send_follow(&community.actor_id, &conn)?;
548 user.send_unfollow(&community.actor_id, &conn)?;
549 match CommunityFollower::unfollow(&conn, &community_follower_form) {
551 Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
554 // TODO: this needs to return a "pending" state, until Accept is received from the remote server
557 let community_view = CommunityView::read(&conn, data.community_id, Some(user_id))?;
559 Ok(CommunityResponse {
560 community: community_view,
565 impl Perform for Oper<GetFollowedCommunities> {
566 type Response = GetFollowedCommunitiesResponse;
570 pool: Pool<ConnectionManager<PgConnection>>,
571 _websocket_info: Option<WebsocketInfo>,
572 ) -> Result<GetFollowedCommunitiesResponse, Error> {
573 let data: &GetFollowedCommunities = &self.data;
575 let claims = match Claims::decode(&data.auth) {
576 Ok(claims) => claims.claims,
577 Err(_e) => return Err(APIError::err("not_logged_in").into()),
580 let user_id = claims.id;
582 let conn = pool.get()?;
584 let communities: Vec<CommunityFollowerView> =
585 match CommunityFollowerView::for_user(&conn, user_id) {
586 Ok(communities) => communities,
587 Err(_e) => return Err(APIError::err("system_err_login").into()),
591 Ok(GetFollowedCommunitiesResponse { communities })
595 impl Perform for Oper<BanFromCommunity> {
596 type Response = BanFromCommunityResponse;
600 pool: Pool<ConnectionManager<PgConnection>>,
601 websocket_info: Option<WebsocketInfo>,
602 ) -> Result<BanFromCommunityResponse, Error> {
603 let data: &BanFromCommunity = &self.data;
605 let claims = match Claims::decode(&data.auth) {
606 Ok(claims) => claims.claims,
607 Err(_e) => return Err(APIError::err("not_logged_in").into()),
610 let user_id = claims.id;
612 let community_user_ban_form = CommunityUserBanForm {
613 community_id: data.community_id,
614 user_id: data.user_id,
617 let conn = pool.get()?;
620 match CommunityUserBan::ban(&conn, &community_user_ban_form) {
622 Err(_e) => return Err(APIError::err("community_user_already_banned").into()),
625 match CommunityUserBan::unban(&conn, &community_user_ban_form) {
627 Err(_e) => return Err(APIError::err("community_user_already_banned").into()),
632 let expires = match data.expires {
633 Some(time) => Some(naive_from_unix(time)),
637 let form = ModBanFromCommunityForm {
638 mod_user_id: user_id,
639 other_user_id: data.user_id,
640 community_id: data.community_id,
641 reason: data.reason.to_owned(),
642 banned: Some(data.ban),
645 ModBanFromCommunity::create(&conn, &form)?;
647 let user_view = UserView::read(&conn, data.user_id)?;
649 let res = BanFromCommunityResponse {
654 if let Some(ws) = websocket_info {
655 ws.chatserver.do_send(SendCommunityRoomMessage {
656 op: UserOperation::BanFromCommunity,
657 response: res.clone(),
658 community_id: data.community_id,
667 impl Perform for Oper<AddModToCommunity> {
668 type Response = AddModToCommunityResponse;
672 pool: Pool<ConnectionManager<PgConnection>>,
673 websocket_info: Option<WebsocketInfo>,
674 ) -> Result<AddModToCommunityResponse, Error> {
675 let data: &AddModToCommunity = &self.data;
677 let claims = match Claims::decode(&data.auth) {
678 Ok(claims) => claims.claims,
679 Err(_e) => return Err(APIError::err("not_logged_in").into()),
682 let user_id = claims.id;
684 let community_moderator_form = CommunityModeratorForm {
685 community_id: data.community_id,
686 user_id: data.user_id,
689 let conn = pool.get()?;
692 match CommunityModerator::join(&conn, &community_moderator_form) {
694 Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
697 match CommunityModerator::leave(&conn, &community_moderator_form) {
699 Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
704 let form = ModAddCommunityForm {
705 mod_user_id: user_id,
706 other_user_id: data.user_id,
707 community_id: data.community_id,
708 removed: Some(!data.added),
710 ModAddCommunity::create(&conn, &form)?;
712 let moderators = CommunityModeratorView::for_community(&conn, data.community_id)?;
714 let res = AddModToCommunityResponse { moderators };
716 if let Some(ws) = websocket_info {
717 ws.chatserver.do_send(SendCommunityRoomMessage {
718 op: UserOperation::AddModToCommunity,
719 response: res.clone(),
720 community_id: data.community_id,
729 impl Perform for Oper<TransferCommunity> {
730 type Response = GetCommunityResponse;
734 pool: Pool<ConnectionManager<PgConnection>>,
735 _websocket_info: Option<WebsocketInfo>,
736 ) -> Result<GetCommunityResponse, Error> {
737 let data: &TransferCommunity = &self.data;
739 let claims = match Claims::decode(&data.auth) {
740 Ok(claims) => claims.claims,
741 Err(_e) => return Err(APIError::err("not_logged_in").into()),
744 let user_id = claims.id;
746 let conn = pool.get()?;
748 let read_community = Community::read(&conn, data.community_id)?;
750 let site_creator_id = Site::read(&conn, 1)?.creator_id;
751 let mut admins = UserView::admins(&conn)?;
752 let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
753 let creator_user = admins.remove(creator_index);
754 admins.insert(0, creator_user);
756 // Make sure user is the creator, or an admin
757 if user_id != read_community.creator_id && !admins.iter().map(|a| a.id).any(|x| x == user_id) {
758 return Err(APIError::err("not_an_admin").into());
761 let community_form = CommunityForm {
762 name: read_community.name,
763 title: read_community.title,
764 description: read_community.description,
765 category_id: read_community.category_id,
766 creator_id: data.user_id, // This makes the new user the community creator
769 nsfw: read_community.nsfw,
770 updated: Some(naive_now()),
771 actor_id: read_community.actor_id,
772 local: read_community.local,
773 private_key: read_community.private_key,
774 public_key: read_community.public_key,
775 last_refreshed_at: None,
779 let _updated_community = match Community::update(&conn, data.community_id, &community_form) {
780 Ok(community) => community,
781 Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
784 // You also have to re-do the community_moderator table, reordering it.
785 let mut community_mods = CommunityModeratorView::for_community(&conn, data.community_id)?;
786 let creator_index = community_mods
788 .position(|r| r.user_id == data.user_id)
790 let creator_user = community_mods.remove(creator_index);
791 community_mods.insert(0, creator_user);
793 CommunityModerator::delete_for_community(&conn, data.community_id)?;
795 for cmod in &community_mods {
796 let community_moderator_form = CommunityModeratorForm {
797 community_id: cmod.community_id,
798 user_id: cmod.user_id,
801 let _inserted_community_moderator =
802 match CommunityModerator::join(&conn, &community_moderator_form) {
804 Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
809 let form = ModAddCommunityForm {
810 mod_user_id: user_id,
811 other_user_id: data.user_id,
812 community_id: data.community_id,
813 removed: Some(false),
815 ModAddCommunity::create(&conn, &form)?;
817 let community_view = match CommunityView::read(&conn, data.community_id, Some(user_id)) {
818 Ok(community) => community,
819 Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
822 let moderators = match CommunityModeratorView::for_community(&conn, data.community_id) {
823 Ok(moderators) => moderators,
824 Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
828 Ok(GetCommunityResponse {
829 community: community_view,