3 api::{APIError, Oper, Perform},
5 extensions::signatures::generate_actor_keypair,
10 db::{Bannable, Crud, Followable, Joinable, SortType},
11 is_valid_community_name,
17 server::{JoinCommunityRoom, SendCommunityRoomMessage},
23 r2d2::{ConnectionManager, Pool},
27 use serde::{Deserialize, Serialize};
28 use std::str::FromStr;
30 #[derive(Serialize, Deserialize)]
31 pub struct GetCommunity {
33 pub name: Option<String>,
37 #[derive(Serialize, Deserialize)]
38 pub struct GetCommunityResponse {
39 pub community: CommunityView,
40 pub moderators: Vec<CommunityModeratorView>,
41 pub admins: Vec<UserView>,
45 #[derive(Serialize, Deserialize)]
46 pub struct CreateCommunity {
49 description: Option<String>,
55 #[derive(Serialize, Deserialize, Clone)]
56 pub struct CommunityResponse {
57 pub community: CommunityView,
60 #[derive(Serialize, Deserialize, Debug)]
61 pub struct ListCommunities {
63 pub page: Option<i64>,
64 pub limit: Option<i64>,
65 pub auth: Option<String>,
68 #[derive(Serialize, Deserialize, Debug)]
69 pub struct ListCommunitiesResponse {
70 pub communities: Vec<CommunityView>,
73 #[derive(Serialize, Deserialize, Clone)]
74 pub struct BanFromCommunity {
75 pub community_id: i32,
78 reason: Option<String>,
83 #[derive(Serialize, Deserialize, Clone)]
84 pub struct BanFromCommunityResponse {
89 #[derive(Serialize, Deserialize)]
90 pub struct AddModToCommunity {
91 pub community_id: i32,
97 #[derive(Serialize, Deserialize, Clone)]
98 pub struct AddModToCommunityResponse {
99 moderators: Vec<CommunityModeratorView>,
102 #[derive(Serialize, Deserialize)]
103 pub struct EditCommunity {
107 description: Option<String>,
109 removed: Option<bool>,
110 deleted: Option<bool>,
112 reason: Option<String>,
113 expires: Option<i64>,
117 #[derive(Serialize, Deserialize)]
118 pub struct FollowCommunity {
124 #[derive(Serialize, Deserialize)]
125 pub struct GetFollowedCommunities {
129 #[derive(Serialize, Deserialize)]
130 pub struct GetFollowedCommunitiesResponse {
131 communities: Vec<CommunityFollowerView>,
134 #[derive(Serialize, Deserialize)]
135 pub struct TransferCommunity {
141 impl Perform for Oper<GetCommunity> {
142 type Response = GetCommunityResponse;
146 pool: Pool<ConnectionManager<PgConnection>>,
147 websocket_info: Option<WebsocketInfo>,
148 ) -> Result<GetCommunityResponse, Error> {
149 let data: &GetCommunity = &self.data;
151 let user_id: Option<i32> = match &data.auth {
152 Some(auth) => match Claims::decode(&auth) {
154 let user_id = claims.claims.id;
162 let conn = pool.get()?;
164 let community = match data.id {
165 Some(id) => Community::read(&conn, id)?,
167 match Community::read_from_name(
169 &data.name.to_owned().unwrap_or_else(|| "main".to_string()),
171 Ok(community) => community,
172 Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
177 let community_view = match CommunityView::read(&conn, community.id, user_id) {
178 Ok(community) => community,
179 Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
182 let moderators = match CommunityModeratorView::for_community(&conn, community.id) {
183 Ok(moderators) => moderators,
184 Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
187 let site_creator_id = Site::read(&conn, 1)?.creator_id;
188 let mut admins = UserView::admins(&conn)?;
189 let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
190 let creator_user = admins.remove(creator_index);
191 admins.insert(0, creator_user);
193 let online = if let Some(ws) = websocket_info {
194 if let Some(id) = ws.id {
195 ws.chatserver.do_send(JoinCommunityRoom {
196 community_id: community.id,
204 // ws.chatserver.send(GetCommunityUsersOnline {community_id}).await.unwrap()
206 // Runtime::new().unwrap().block_on(fut)
211 let res = GetCommunityResponse {
212 community: community_view,
223 impl Perform for Oper<CreateCommunity> {
224 type Response = CommunityResponse;
228 pool: Pool<ConnectionManager<PgConnection>>,
229 _websocket_info: Option<WebsocketInfo>,
230 ) -> Result<CommunityResponse, Error> {
231 let data: &CreateCommunity = &self.data;
233 let claims = match Claims::decode(&data.auth) {
234 Ok(claims) => claims.claims,
235 Err(_e) => return Err(APIError::err("not_logged_in").into()),
238 if let Err(slurs) = slur_check(&data.name) {
239 return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
242 if let Err(slurs) = slur_check(&data.title) {
243 return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
246 if let Some(description) = &data.description {
247 if let Err(slurs) = slur_check(description) {
248 return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
252 if !is_valid_community_name(&data.name) {
253 return Err(APIError::err("invalid_community_name").into());
256 let user_id = claims.id;
258 let conn = pool.get()?;
260 // Check for a site ban
261 if UserView::read(&conn, user_id)?.banned {
262 return Err(APIError::err("site_ban").into());
265 // When you create a community, make sure the user becomes a moderator and a follower
266 let keypair = generate_actor_keypair()?;
268 let community_form = CommunityForm {
269 name: data.name.to_owned(),
270 title: data.title.to_owned(),
271 description: data.description.to_owned(),
272 category_id: data.category_id,
278 actor_id: make_apub_endpoint(EndpointType::Community, &data.name).to_string(),
280 private_key: Some(keypair.private_key),
281 public_key: Some(keypair.public_key),
282 last_refreshed_at: None,
286 let inserted_community = match Community::create(&conn, &community_form) {
287 Ok(community) => community,
288 Err(_e) => return Err(APIError::err("community_already_exists").into()),
291 let community_moderator_form = CommunityModeratorForm {
292 community_id: inserted_community.id,
296 let _inserted_community_moderator =
297 match CommunityModerator::join(&conn, &community_moderator_form) {
299 Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
302 let community_follower_form = CommunityFollowerForm {
303 community_id: inserted_community.id,
307 let _inserted_community_follower =
308 match CommunityFollower::follow(&conn, &community_follower_form) {
310 Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
313 let community_view = CommunityView::read(&conn, inserted_community.id, Some(user_id))?;
315 Ok(CommunityResponse {
316 community: community_view,
321 impl Perform for Oper<EditCommunity> {
322 type Response = CommunityResponse;
326 pool: Pool<ConnectionManager<PgConnection>>,
327 websocket_info: Option<WebsocketInfo>,
328 ) -> Result<CommunityResponse, Error> {
329 let data: &EditCommunity = &self.data;
331 if let Err(slurs) = slur_check(&data.name) {
332 return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
335 if let Err(slurs) = slur_check(&data.title) {
336 return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
339 if let Some(description) = &data.description {
340 if let Err(slurs) = slur_check(description) {
341 return Err(APIError::err(&slurs_vec_to_str(slurs)).into());
345 let claims = match Claims::decode(&data.auth) {
346 Ok(claims) => claims.claims,
347 Err(_e) => return Err(APIError::err("not_logged_in").into()),
350 if !is_valid_community_name(&data.name) {
351 return Err(APIError::err("invalid_community_name").into());
354 let user_id = claims.id;
356 let conn = pool.get()?;
358 // Check for a site ban
359 let user = User_::read(&conn, user_id)?;
361 return Err(APIError::err("site_ban").into());
365 let mut editors: Vec<i32> = Vec::new();
367 &mut CommunityModeratorView::for_community(&conn, data.edit_id)?
372 editors.append(&mut UserView::admins(&conn)?.into_iter().map(|a| a.id).collect());
373 if !editors.contains(&user_id) {
374 return Err(APIError::err("no_community_edit_allowed").into());
377 let read_community = Community::read(&conn, data.edit_id)?;
379 let community_form = CommunityForm {
380 name: data.name.to_owned(),
381 title: data.title.to_owned(),
382 description: data.description.to_owned(),
383 category_id: data.category_id.to_owned(),
385 removed: data.removed.to_owned(),
386 deleted: data.deleted.to_owned(),
388 updated: Some(naive_now()),
389 actor_id: read_community.actor_id,
390 local: read_community.local,
391 private_key: read_community.private_key,
392 public_key: read_community.public_key,
393 last_refreshed_at: None,
397 let updated_community = match Community::update(&conn, data.edit_id, &community_form) {
398 Ok(community) => community,
399 Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
403 if let Some(removed) = data.removed.to_owned() {
404 let expires = match data.expires {
405 Some(time) => Some(naive_from_unix(time)),
408 let form = ModRemoveCommunityForm {
409 mod_user_id: user_id,
410 community_id: data.edit_id,
411 removed: Some(removed),
412 reason: data.reason.to_owned(),
415 ModRemoveCommunity::create(&conn, &form)?;
418 if let Some(deleted) = data.deleted.to_owned() {
420 updated_community.send_delete(&user, &conn)?;
422 updated_community.send_undo_delete(&user, &conn)?;
424 } else if let Some(removed) = data.removed.to_owned() {
426 updated_community.send_remove(&user, &conn)?;
428 updated_community.send_undo_remove(&user, &conn)?;
432 let community_view = CommunityView::read(&conn, data.edit_id, Some(user_id))?;
434 let res = CommunityResponse {
435 community: community_view,
438 if let Some(ws) = websocket_info {
439 // Strip out the user id and subscribed when sending to others
440 let mut res_sent = res.clone();
441 res_sent.community.user_id = None;
442 res_sent.community.subscribed = None;
444 ws.chatserver.do_send(SendCommunityRoomMessage {
445 op: UserOperation::EditCommunity,
447 community_id: data.edit_id,
456 impl Perform for Oper<ListCommunities> {
457 type Response = ListCommunitiesResponse;
461 pool: Pool<ConnectionManager<PgConnection>>,
462 _websocket_info: Option<WebsocketInfo>,
463 ) -> Result<ListCommunitiesResponse, Error> {
464 let data: &ListCommunities = &self.data;
466 let user_claims: Option<Claims> = match &data.auth {
467 Some(auth) => match Claims::decode(&auth) {
468 Ok(claims) => Some(claims.claims),
474 let user_id = match &user_claims {
475 Some(claims) => Some(claims.id),
479 let show_nsfw = match &user_claims {
480 Some(claims) => claims.show_nsfw,
484 let sort = SortType::from_str(&data.sort)?;
486 let conn = pool.get()?;
488 let communities = CommunityQueryBuilder::create(&conn)
491 .show_nsfw(show_nsfw)
497 Ok(ListCommunitiesResponse { communities })
501 impl Perform for Oper<FollowCommunity> {
502 type Response = CommunityResponse;
506 pool: Pool<ConnectionManager<PgConnection>>,
507 _websocket_info: Option<WebsocketInfo>,
508 ) -> Result<CommunityResponse, Error> {
509 let data: &FollowCommunity = &self.data;
511 let claims = match Claims::decode(&data.auth) {
512 Ok(claims) => claims.claims,
513 Err(_e) => return Err(APIError::err("not_logged_in").into()),
516 let user_id = claims.id;
518 let conn = pool.get()?;
520 let community = Community::read(&conn, data.community_id)?;
521 let community_follower_form = CommunityFollowerForm {
522 community_id: data.community_id,
528 match CommunityFollower::follow(&conn, &community_follower_form) {
530 Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
533 match CommunityFollower::unfollow(&conn, &community_follower_form) {
535 Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
539 let user = User_::read(&conn, user_id)?;
542 // Dont actually add to the community followers here, because you need
543 // to wait for the accept
544 user.send_follow(&community.actor_id, &conn)?;
546 user.send_unfollow(&community.actor_id, &conn)?;
547 match CommunityFollower::unfollow(&conn, &community_follower_form) {
549 Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
552 // TODO: this needs to return a "pending" state, until Accept is received from the remote server
555 let community_view = CommunityView::read(&conn, data.community_id, Some(user_id))?;
557 Ok(CommunityResponse {
558 community: community_view,
563 impl Perform for Oper<GetFollowedCommunities> {
564 type Response = GetFollowedCommunitiesResponse;
568 pool: Pool<ConnectionManager<PgConnection>>,
569 _websocket_info: Option<WebsocketInfo>,
570 ) -> Result<GetFollowedCommunitiesResponse, Error> {
571 let data: &GetFollowedCommunities = &self.data;
573 let claims = match Claims::decode(&data.auth) {
574 Ok(claims) => claims.claims,
575 Err(_e) => return Err(APIError::err("not_logged_in").into()),
578 let user_id = claims.id;
580 let conn = pool.get()?;
582 let communities: Vec<CommunityFollowerView> =
583 match CommunityFollowerView::for_user(&conn, user_id) {
584 Ok(communities) => communities,
585 Err(_e) => return Err(APIError::err("system_err_login").into()),
589 Ok(GetFollowedCommunitiesResponse { communities })
593 impl Perform for Oper<BanFromCommunity> {
594 type Response = BanFromCommunityResponse;
598 pool: Pool<ConnectionManager<PgConnection>>,
599 websocket_info: Option<WebsocketInfo>,
600 ) -> Result<BanFromCommunityResponse, Error> {
601 let data: &BanFromCommunity = &self.data;
603 let claims = match Claims::decode(&data.auth) {
604 Ok(claims) => claims.claims,
605 Err(_e) => return Err(APIError::err("not_logged_in").into()),
608 let user_id = claims.id;
610 let community_user_ban_form = CommunityUserBanForm {
611 community_id: data.community_id,
612 user_id: data.user_id,
615 let conn = pool.get()?;
618 match CommunityUserBan::ban(&conn, &community_user_ban_form) {
620 Err(_e) => return Err(APIError::err("community_user_already_banned").into()),
623 match CommunityUserBan::unban(&conn, &community_user_ban_form) {
625 Err(_e) => return Err(APIError::err("community_user_already_banned").into()),
630 let expires = match data.expires {
631 Some(time) => Some(naive_from_unix(time)),
635 let form = ModBanFromCommunityForm {
636 mod_user_id: user_id,
637 other_user_id: data.user_id,
638 community_id: data.community_id,
639 reason: data.reason.to_owned(),
640 banned: Some(data.ban),
643 ModBanFromCommunity::create(&conn, &form)?;
645 let user_view = UserView::read(&conn, data.user_id)?;
647 let res = BanFromCommunityResponse {
652 if let Some(ws) = websocket_info {
653 ws.chatserver.do_send(SendCommunityRoomMessage {
654 op: UserOperation::BanFromCommunity,
655 response: res.clone(),
656 community_id: data.community_id,
665 impl Perform for Oper<AddModToCommunity> {
666 type Response = AddModToCommunityResponse;
670 pool: Pool<ConnectionManager<PgConnection>>,
671 websocket_info: Option<WebsocketInfo>,
672 ) -> Result<AddModToCommunityResponse, Error> {
673 let data: &AddModToCommunity = &self.data;
675 let claims = match Claims::decode(&data.auth) {
676 Ok(claims) => claims.claims,
677 Err(_e) => return Err(APIError::err("not_logged_in").into()),
680 let user_id = claims.id;
682 let community_moderator_form = CommunityModeratorForm {
683 community_id: data.community_id,
684 user_id: data.user_id,
687 let conn = pool.get()?;
690 match CommunityModerator::join(&conn, &community_moderator_form) {
692 Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
695 match CommunityModerator::leave(&conn, &community_moderator_form) {
697 Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
702 let form = ModAddCommunityForm {
703 mod_user_id: user_id,
704 other_user_id: data.user_id,
705 community_id: data.community_id,
706 removed: Some(!data.added),
708 ModAddCommunity::create(&conn, &form)?;
710 let moderators = CommunityModeratorView::for_community(&conn, data.community_id)?;
712 let res = AddModToCommunityResponse { moderators };
714 if let Some(ws) = websocket_info {
715 ws.chatserver.do_send(SendCommunityRoomMessage {
716 op: UserOperation::AddModToCommunity,
717 response: res.clone(),
718 community_id: data.community_id,
727 impl Perform for Oper<TransferCommunity> {
728 type Response = GetCommunityResponse;
732 pool: Pool<ConnectionManager<PgConnection>>,
733 _websocket_info: Option<WebsocketInfo>,
734 ) -> Result<GetCommunityResponse, Error> {
735 let data: &TransferCommunity = &self.data;
737 let claims = match Claims::decode(&data.auth) {
738 Ok(claims) => claims.claims,
739 Err(_e) => return Err(APIError::err("not_logged_in").into()),
742 let user_id = claims.id;
744 let conn = pool.get()?;
746 let read_community = Community::read(&conn, data.community_id)?;
748 let site_creator_id = Site::read(&conn, 1)?.creator_id;
749 let mut admins = UserView::admins(&conn)?;
750 let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
751 let creator_user = admins.remove(creator_index);
752 admins.insert(0, creator_user);
754 // Make sure user is the creator, or an admin
755 if user_id != read_community.creator_id && !admins.iter().map(|a| a.id).any(|x| x == user_id) {
756 return Err(APIError::err("not_an_admin").into());
759 let community_form = CommunityForm {
760 name: read_community.name,
761 title: read_community.title,
762 description: read_community.description,
763 category_id: read_community.category_id,
764 creator_id: data.user_id, // This makes the new user the community creator
767 nsfw: read_community.nsfw,
768 updated: Some(naive_now()),
769 actor_id: read_community.actor_id,
770 local: read_community.local,
771 private_key: read_community.private_key,
772 public_key: read_community.public_key,
773 last_refreshed_at: None,
777 let _updated_community = match Community::update(&conn, data.community_id, &community_form) {
778 Ok(community) => community,
779 Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
782 // You also have to re-do the community_moderator table, reordering it.
783 let mut community_mods = CommunityModeratorView::for_community(&conn, data.community_id)?;
784 let creator_index = community_mods
786 .position(|r| r.user_id == data.user_id)
788 let creator_user = community_mods.remove(creator_index);
789 community_mods.insert(0, creator_user);
791 CommunityModerator::delete_for_community(&conn, data.community_id)?;
793 for cmod in &community_mods {
794 let community_moderator_form = CommunityModeratorForm {
795 community_id: cmod.community_id,
796 user_id: cmod.user_id,
799 let _inserted_community_moderator =
800 match CommunityModerator::join(&conn, &community_moderator_form) {
802 Err(_e) => return Err(APIError::err("community_moderator_already_exists").into()),
807 let form = ModAddCommunityForm {
808 mod_user_id: user_id,
809 other_user_id: data.user_id,
810 community_id: data.community_id,
811 removed: Some(false),
813 ModAddCommunity::create(&conn, &form)?;
815 let community_view = match CommunityView::read(&conn, data.community_id, Some(user_id)) {
816 Ok(community) => community,
817 Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
820 let moderators = match CommunityModeratorView::for_community(&conn, data.community_id) {
821 Ok(moderators) => moderators,
822 Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
826 Ok(GetCommunityResponse {
827 community: community_view,