]> Untitled Git - lemmy.git/blob - src/api_routes.rs
Merge pull request #2593 from LemmyNet/refactor-notifications
[lemmy.git] / src / api_routes.rs
1 use actix_web::{guard, web, Error, HttpResponse, Result};
2 use lemmy_api::Perform;
3 use lemmy_api_common::{
4   comment::{
5     CreateComment,
6     CreateCommentLike,
7     CreateCommentReport,
8     DeleteComment,
9     EditComment,
10     GetComment,
11     GetComments,
12     ListCommentReports,
13     RemoveComment,
14     ResolveCommentReport,
15     SaveComment,
16   },
17   community::{
18     AddModToCommunity,
19     BanFromCommunity,
20     BlockCommunity,
21     CreateCommunity,
22     DeleteCommunity,
23     EditCommunity,
24     FollowCommunity,
25     GetCommunity,
26     HideCommunity,
27     ListCommunities,
28     RemoveCommunity,
29     TransferCommunity,
30   },
31   context::LemmyContext,
32   person::{
33     AddAdmin,
34     BanPerson,
35     BlockPerson,
36     ChangePassword,
37     DeleteAccount,
38     GetBannedPersons,
39     GetCaptcha,
40     GetPersonDetails,
41     GetPersonMentions,
42     GetReplies,
43     GetReportCount,
44     GetUnreadCount,
45     Login,
46     MarkAllAsRead,
47     MarkCommentReplyAsRead,
48     MarkPersonMentionAsRead,
49     PasswordChangeAfterReset,
50     PasswordReset,
51     Register,
52     SaveUserSettings,
53     VerifyEmail,
54   },
55   post::{
56     CreatePost,
57     CreatePostLike,
58     CreatePostReport,
59     DeletePost,
60     EditPost,
61     GetPost,
62     GetPosts,
63     GetSiteMetadata,
64     ListPostReports,
65     LockPost,
66     MarkPostAsRead,
67     RemovePost,
68     ResolvePostReport,
69     SavePost,
70     StickyPost,
71   },
72   private_message::{
73     CreatePrivateMessage,
74     CreatePrivateMessageReport,
75     DeletePrivateMessage,
76     EditPrivateMessage,
77     GetPrivateMessages,
78     ListPrivateMessageReports,
79     MarkPrivateMessageAsRead,
80     ResolvePrivateMessageReport,
81   },
82   site::{
83     ApproveRegistrationApplication,
84     CreateSite,
85     EditSite,
86     GetModlog,
87     GetSite,
88     GetUnreadRegistrationApplicationCount,
89     LeaveAdmin,
90     ListRegistrationApplications,
91     PurgeComment,
92     PurgeCommunity,
93     PurgePerson,
94     PurgePost,
95     ResolveObject,
96     Search,
97   },
98   websocket::{
99     routes::chat_route,
100     serialize_websocket_message,
101     structs::{CommunityJoin, ModJoin, PostJoin, UserJoin},
102     UserOperation,
103     UserOperationApub,
104     UserOperationCrud,
105   },
106 };
107 use lemmy_api_crud::PerformCrud;
108 use lemmy_apub::{api::PerformApub, SendActivity};
109 use lemmy_utils::{error::LemmyError, rate_limit::RateLimitCell, ConnectionId};
110 use serde::Deserialize;
111 use std::result;
112
113 pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
114   cfg.service(
115     web::scope("/api/v3")
116       // Websocket
117       .service(web::resource("/ws").to(chat_route))
118       // Site
119       .service(
120         web::scope("/site")
121           .wrap(rate_limit.message())
122           .route("", web::get().to(route_get_crud::<GetSite>))
123           // Admin Actions
124           .route("", web::post().to(route_post_crud::<CreateSite>))
125           .route("", web::put().to(route_post_crud::<EditSite>)),
126       )
127       .service(
128         web::resource("/modlog")
129           .wrap(rate_limit.message())
130           .route(web::get().to(route_get::<GetModlog>)),
131       )
132       .service(
133         web::resource("/search")
134           .wrap(rate_limit.search())
135           .route(web::get().to(route_get_apub::<Search>)),
136       )
137       .service(
138         web::resource("/resolve_object")
139           .wrap(rate_limit.message())
140           .route(web::get().to(route_get_apub::<ResolveObject>)),
141       )
142       // Community
143       .service(
144         web::resource("/community")
145           .guard(guard::Post())
146           .wrap(rate_limit.register())
147           .route(web::post().to(route_post_crud::<CreateCommunity>)),
148       )
149       .service(
150         web::scope("/community")
151           .wrap(rate_limit.message())
152           .route("", web::get().to(route_get_apub::<GetCommunity>))
153           .route("", web::put().to(route_post_crud::<EditCommunity>))
154           .route("/hide", web::put().to(route_post::<HideCommunity>))
155           .route("/list", web::get().to(route_get_crud::<ListCommunities>))
156           .route("/follow", web::post().to(route_post::<FollowCommunity>))
157           .route("/block", web::post().to(route_post::<BlockCommunity>))
158           .route(
159             "/delete",
160             web::post().to(route_post_crud::<DeleteCommunity>),
161           )
162           // Mod Actions
163           .route(
164             "/remove",
165             web::post().to(route_post_crud::<RemoveCommunity>),
166           )
167           .route("/transfer", web::post().to(route_post::<TransferCommunity>))
168           .route("/ban_user", web::post().to(route_post::<BanFromCommunity>))
169           .route("/mod", web::post().to(route_post::<AddModToCommunity>))
170           .route("/join", web::post().to(route_post::<CommunityJoin>))
171           .route("/mod/join", web::post().to(route_post::<ModJoin>)),
172       )
173       // Post
174       .service(
175         // Handle POST to /post separately to add the post() rate limitter
176         web::resource("/post")
177           .guard(guard::Post())
178           .wrap(rate_limit.post())
179           .route(web::post().to(route_post_crud::<CreatePost>)),
180       )
181       .service(
182         web::scope("/post")
183           .wrap(rate_limit.message())
184           .route("", web::get().to(route_get_crud::<GetPost>))
185           .route("", web::put().to(route_post_crud::<EditPost>))
186           .route("/delete", web::post().to(route_post_crud::<DeletePost>))
187           .route("/remove", web::post().to(route_post_crud::<RemovePost>))
188           .route(
189             "/mark_as_read",
190             web::post().to(route_post::<MarkPostAsRead>),
191           )
192           .route("/lock", web::post().to(route_post::<LockPost>))
193           .route("/sticky", web::post().to(route_post::<StickyPost>))
194           .route("/list", web::get().to(route_get_apub::<GetPosts>))
195           .route("/like", web::post().to(route_post::<CreatePostLike>))
196           .route("/save", web::put().to(route_post::<SavePost>))
197           .route("/join", web::post().to(route_post::<PostJoin>))
198           .route("/report", web::post().to(route_post::<CreatePostReport>))
199           .route(
200             "/report/resolve",
201             web::put().to(route_post::<ResolvePostReport>),
202           )
203           .route("/report/list", web::get().to(route_get::<ListPostReports>))
204           .route(
205             "/site_metadata",
206             web::get().to(route_get::<GetSiteMetadata>),
207           ),
208       )
209       // Comment
210       .service(
211         // Handle POST to /comment separately to add the comment() rate limitter
212         web::resource("/comment")
213           .guard(guard::Post())
214           .wrap(rate_limit.comment())
215           .route(web::post().to(route_post_crud::<CreateComment>)),
216       )
217       .service(
218         web::scope("/comment")
219           .wrap(rate_limit.message())
220           .route("", web::get().to(route_get_crud::<GetComment>))
221           .route("", web::put().to(route_post_crud::<EditComment>))
222           .route("/delete", web::post().to(route_post_crud::<DeleteComment>))
223           .route("/remove", web::post().to(route_post_crud::<RemoveComment>))
224           .route(
225             "/mark_as_read",
226             web::post().to(route_post::<MarkCommentReplyAsRead>),
227           )
228           .route("/like", web::post().to(route_post::<CreateCommentLike>))
229           .route("/save", web::put().to(route_post::<SaveComment>))
230           .route("/list", web::get().to(route_get_apub::<GetComments>))
231           .route("/report", web::post().to(route_post::<CreateCommentReport>))
232           .route(
233             "/report/resolve",
234             web::put().to(route_post::<ResolveCommentReport>),
235           )
236           .route(
237             "/report/list",
238             web::get().to(route_get::<ListCommentReports>),
239           ),
240       )
241       // Private Message
242       .service(
243         web::scope("/private_message")
244           .wrap(rate_limit.message())
245           .route("/list", web::get().to(route_get_crud::<GetPrivateMessages>))
246           .route("", web::post().to(route_post_crud::<CreatePrivateMessage>))
247           .route("", web::put().to(route_post_crud::<EditPrivateMessage>))
248           .route(
249             "/delete",
250             web::post().to(route_post_crud::<DeletePrivateMessage>),
251           )
252           .route(
253             "/mark_as_read",
254             web::post().to(route_post::<MarkPrivateMessageAsRead>),
255           )
256           .route(
257             "/report",
258             web::post().to(route_post::<CreatePrivateMessageReport>),
259           )
260           .route(
261             "/report/resolve",
262             web::put().to(route_post::<ResolvePrivateMessageReport>),
263           )
264           .route(
265             "/report/list",
266             web::get().to(route_get::<ListPrivateMessageReports>),
267           ),
268       )
269       // User
270       .service(
271         // Account action, I don't like that it's in /user maybe /accounts
272         // Handle /user/register separately to add the register() rate limitter
273         web::resource("/user/register")
274           .guard(guard::Post())
275           .wrap(rate_limit.register())
276           .route(web::post().to(route_post_crud::<Register>)),
277       )
278       .service(
279         // Handle captcha separately
280         web::resource("/user/get_captcha")
281           .wrap(rate_limit.post())
282           .route(web::get().to(route_get::<GetCaptcha>)),
283       )
284       // User actions
285       .service(
286         web::scope("/user")
287           .wrap(rate_limit.message())
288           .route("", web::get().to(route_get_apub::<GetPersonDetails>))
289           .route("/mention", web::get().to(route_get::<GetPersonMentions>))
290           .route(
291             "/mention/mark_as_read",
292             web::post().to(route_post::<MarkPersonMentionAsRead>),
293           )
294           .route("/replies", web::get().to(route_get::<GetReplies>))
295           .route("/join", web::post().to(route_post::<UserJoin>))
296           // Admin action. I don't like that it's in /user
297           .route("/ban", web::post().to(route_post::<BanPerson>))
298           .route("/banned", web::get().to(route_get::<GetBannedPersons>))
299           .route("/block", web::post().to(route_post::<BlockPerson>))
300           // Account actions. I don't like that they're in /user maybe /accounts
301           .route("/login", web::post().to(route_post::<Login>))
302           .route(
303             "/delete_account",
304             web::post().to(route_post_crud::<DeleteAccount>),
305           )
306           .route(
307             "/password_reset",
308             web::post().to(route_post::<PasswordReset>),
309           )
310           .route(
311             "/password_change",
312             web::post().to(route_post::<PasswordChangeAfterReset>),
313           )
314           // mark_all_as_read feels off being in this section as well
315           .route(
316             "/mark_all_as_read",
317             web::post().to(route_post::<MarkAllAsRead>),
318           )
319           .route(
320             "/save_user_settings",
321             web::put().to(route_post::<SaveUserSettings>),
322           )
323           .route(
324             "/change_password",
325             web::put().to(route_post::<ChangePassword>),
326           )
327           .route("/report_count", web::get().to(route_get::<GetReportCount>))
328           .route("/unread_count", web::get().to(route_get::<GetUnreadCount>))
329           .route("/verify_email", web::post().to(route_post::<VerifyEmail>))
330           .route("/leave_admin", web::post().to(route_post::<LeaveAdmin>)),
331       )
332       // Admin Actions
333       .service(
334         web::scope("/admin")
335           .wrap(rate_limit.message())
336           .route("/add", web::post().to(route_post::<AddAdmin>))
337           .route(
338             "/registration_application/count",
339             web::get().to(route_get::<GetUnreadRegistrationApplicationCount>),
340           )
341           .route(
342             "/registration_application/list",
343             web::get().to(route_get::<ListRegistrationApplications>),
344           )
345           .route(
346             "/registration_application/approve",
347             web::put().to(route_post::<ApproveRegistrationApplication>),
348           ),
349       )
350       .service(
351         web::scope("/admin/purge")
352           .wrap(rate_limit.message())
353           .route("/person", web::post().to(route_post::<PurgePerson>))
354           .route("/community", web::post().to(route_post::<PurgeCommunity>))
355           .route("/post", web::post().to(route_post::<PurgePost>))
356           .route("/comment", web::post().to(route_post::<PurgeComment>)),
357       ),
358   );
359 }
360
361 async fn perform<'a, Data>(
362   data: Data,
363   context: web::Data<LemmyContext>,
364 ) -> Result<HttpResponse, Error>
365 where
366   Data: Perform
367     + SendActivity<Response = <Data as Perform>::Response>
368     + Clone
369     + Deserialize<'a>
370     + Send
371     + 'static,
372 {
373   let res = data.perform(&context, None).await?;
374   SendActivity::send_activity(&data, &res, &context).await?;
375   Ok(HttpResponse::Ok().json(res))
376 }
377
378 async fn route_get<'a, Data>(
379   data: web::Query<Data>,
380   context: web::Data<LemmyContext>,
381 ) -> Result<HttpResponse, Error>
382 where
383   Data: Perform
384     + SendActivity<Response = <Data as Perform>::Response>
385     + Clone
386     + Deserialize<'a>
387     + Send
388     + 'static,
389 {
390   perform::<Data>(data.0, context).await
391 }
392
393 async fn route_get_apub<'a, Data>(
394   data: web::Query<Data>,
395   context: web::Data<LemmyContext>,
396 ) -> Result<HttpResponse, Error>
397 where
398   Data: PerformApub
399     + SendActivity<Response = <Data as PerformApub>::Response>
400     + Clone
401     + Deserialize<'a>
402     + Send
403     + 'static,
404 {
405   let res = data.perform(&context, None).await?;
406   SendActivity::send_activity(&data.0, &res, &context).await?;
407   Ok(HttpResponse::Ok().json(res))
408 }
409
410 async fn route_post<'a, Data>(
411   data: web::Json<Data>,
412   context: web::Data<LemmyContext>,
413 ) -> Result<HttpResponse, Error>
414 where
415   Data: Perform
416     + SendActivity<Response = <Data as Perform>::Response>
417     + Clone
418     + Deserialize<'a>
419     + Send
420     + 'static,
421 {
422   perform::<Data>(data.0, context).await
423 }
424
425 async fn perform_crud<'a, Data>(
426   data: Data,
427   context: web::Data<LemmyContext>,
428 ) -> Result<HttpResponse, Error>
429 where
430   Data: PerformCrud
431     + SendActivity<Response = <Data as PerformCrud>::Response>
432     + Clone
433     + Deserialize<'a>
434     + Send
435     + 'static,
436 {
437   let res = data.perform(&context, None).await?;
438   SendActivity::send_activity(&data, &res, &context).await?;
439   Ok(HttpResponse::Ok().json(res))
440 }
441
442 async fn route_get_crud<'a, Data>(
443   data: web::Query<Data>,
444   context: web::Data<LemmyContext>,
445 ) -> Result<HttpResponse, Error>
446 where
447   Data: PerformCrud
448     + SendActivity<Response = <Data as PerformCrud>::Response>
449     + Clone
450     + Deserialize<'a>
451     + Send
452     + 'static,
453 {
454   perform_crud::<Data>(data.0, context).await
455 }
456
457 async fn route_post_crud<'a, Data>(
458   data: web::Json<Data>,
459   context: web::Data<LemmyContext>,
460 ) -> Result<HttpResponse, Error>
461 where
462   Data: PerformCrud
463     + SendActivity<Response = <Data as PerformCrud>::Response>
464     + Clone
465     + Deserialize<'a>
466     + Send
467     + 'static,
468 {
469   perform_crud::<Data>(data.0, context).await
470 }
471
472 pub async fn match_websocket_operation_crud(
473   context: LemmyContext,
474   id: ConnectionId,
475   op: UserOperationCrud,
476   data: &str,
477 ) -> result::Result<String, LemmyError> {
478   match op {
479     // User ops
480     UserOperationCrud::Register => {
481       do_websocket_operation_crud::<Register>(context, id, op, data).await
482     }
483     UserOperationCrud::DeleteAccount => {
484       do_websocket_operation_crud::<DeleteAccount>(context, id, op, data).await
485     }
486
487     // Private Message ops
488     UserOperationCrud::CreatePrivateMessage => {
489       do_websocket_operation_crud::<CreatePrivateMessage>(context, id, op, data).await
490     }
491     UserOperationCrud::EditPrivateMessage => {
492       do_websocket_operation_crud::<EditPrivateMessage>(context, id, op, data).await
493     }
494     UserOperationCrud::DeletePrivateMessage => {
495       do_websocket_operation_crud::<DeletePrivateMessage>(context, id, op, data).await
496     }
497     UserOperationCrud::GetPrivateMessages => {
498       do_websocket_operation_crud::<GetPrivateMessages>(context, id, op, data).await
499     }
500
501     // Site ops
502     UserOperationCrud::CreateSite => {
503       do_websocket_operation_crud::<CreateSite>(context, id, op, data).await
504     }
505     UserOperationCrud::EditSite => {
506       do_websocket_operation_crud::<EditSite>(context, id, op, data).await
507     }
508     UserOperationCrud::GetSite => {
509       do_websocket_operation_crud::<GetSite>(context, id, op, data).await
510     }
511
512     // Community ops
513     UserOperationCrud::ListCommunities => {
514       do_websocket_operation_crud::<ListCommunities>(context, id, op, data).await
515     }
516     UserOperationCrud::CreateCommunity => {
517       do_websocket_operation_crud::<CreateCommunity>(context, id, op, data).await
518     }
519     UserOperationCrud::EditCommunity => {
520       do_websocket_operation_crud::<EditCommunity>(context, id, op, data).await
521     }
522     UserOperationCrud::DeleteCommunity => {
523       do_websocket_operation_crud::<DeleteCommunity>(context, id, op, data).await
524     }
525     UserOperationCrud::RemoveCommunity => {
526       do_websocket_operation_crud::<RemoveCommunity>(context, id, op, data).await
527     }
528
529     // Post ops
530     UserOperationCrud::CreatePost => {
531       do_websocket_operation_crud::<CreatePost>(context, id, op, data).await
532     }
533     UserOperationCrud::GetPost => {
534       do_websocket_operation_crud::<GetPost>(context, id, op, data).await
535     }
536     UserOperationCrud::EditPost => {
537       do_websocket_operation_crud::<EditPost>(context, id, op, data).await
538     }
539     UserOperationCrud::DeletePost => {
540       do_websocket_operation_crud::<DeletePost>(context, id, op, data).await
541     }
542     UserOperationCrud::RemovePost => {
543       do_websocket_operation_crud::<RemovePost>(context, id, op, data).await
544     }
545
546     // Comment ops
547     UserOperationCrud::CreateComment => {
548       do_websocket_operation_crud::<CreateComment>(context, id, op, data).await
549     }
550     UserOperationCrud::EditComment => {
551       do_websocket_operation_crud::<EditComment>(context, id, op, data).await
552     }
553     UserOperationCrud::DeleteComment => {
554       do_websocket_operation_crud::<DeleteComment>(context, id, op, data).await
555     }
556     UserOperationCrud::RemoveComment => {
557       do_websocket_operation_crud::<RemoveComment>(context, id, op, data).await
558     }
559     UserOperationCrud::GetComment => {
560       do_websocket_operation_crud::<GetComment>(context, id, op, data).await
561     }
562   }
563 }
564
565 async fn do_websocket_operation_crud<'a, 'b, Data>(
566   context: LemmyContext,
567   id: ConnectionId,
568   op: UserOperationCrud,
569   data: &str,
570 ) -> result::Result<String, LemmyError>
571 where
572   Data: PerformCrud + SendActivity<Response = <Data as PerformCrud>::Response>,
573   for<'de> Data: Deserialize<'de>,
574 {
575   let parsed_data: Data = serde_json::from_str(data)?;
576   let res = parsed_data
577     .perform(&web::Data::new(context.clone()), Some(id))
578     .await?;
579   SendActivity::send_activity(&parsed_data, &res, &context).await?;
580   serialize_websocket_message(&op, &res)
581 }
582
583 pub async fn match_websocket_operation_apub(
584   context: LemmyContext,
585   id: ConnectionId,
586   op: UserOperationApub,
587   data: &str,
588 ) -> result::Result<String, LemmyError> {
589   match op {
590     UserOperationApub::GetPersonDetails => {
591       do_websocket_operation_apub::<GetPersonDetails>(context, id, op, data).await
592     }
593     UserOperationApub::GetCommunity => {
594       do_websocket_operation_apub::<GetCommunity>(context, id, op, data).await
595     }
596     UserOperationApub::GetComments => {
597       do_websocket_operation_apub::<GetComments>(context, id, op, data).await
598     }
599     UserOperationApub::GetPosts => {
600       do_websocket_operation_apub::<GetPosts>(context, id, op, data).await
601     }
602     UserOperationApub::ResolveObject => {
603       do_websocket_operation_apub::<ResolveObject>(context, id, op, data).await
604     }
605     UserOperationApub::Search => do_websocket_operation_apub::<Search>(context, id, op, data).await,
606   }
607 }
608
609 async fn do_websocket_operation_apub<'a, 'b, Data>(
610   context: LemmyContext,
611   id: ConnectionId,
612   op: UserOperationApub,
613   data: &str,
614 ) -> result::Result<String, LemmyError>
615 where
616   Data: PerformApub + SendActivity<Response = <Data as PerformApub>::Response>,
617   for<'de> Data: Deserialize<'de>,
618 {
619   let parsed_data: Data = serde_json::from_str(data)?;
620   let res = parsed_data
621     .perform(&web::Data::new(context.clone()), Some(id))
622     .await?;
623   SendActivity::send_activity(&parsed_data, &res, &context).await?;
624   serialize_websocket_message(&op, &res)
625 }
626
627 pub async fn match_websocket_operation(
628   context: LemmyContext,
629   id: ConnectionId,
630   op: UserOperation,
631   data: &str,
632 ) -> result::Result<String, LemmyError> {
633   match op {
634     // User ops
635     UserOperation::Login => do_websocket_operation::<Login>(context, id, op, data).await,
636     UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
637     UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
638     UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
639     UserOperation::GetUnreadRegistrationApplicationCount => {
640       do_websocket_operation::<GetUnreadRegistrationApplicationCount>(context, id, op, data).await
641     }
642     UserOperation::ListRegistrationApplications => {
643       do_websocket_operation::<ListRegistrationApplications>(context, id, op, data).await
644     }
645     UserOperation::ApproveRegistrationApplication => {
646       do_websocket_operation::<ApproveRegistrationApplication>(context, id, op, data).await
647     }
648     UserOperation::BanPerson => do_websocket_operation::<BanPerson>(context, id, op, data).await,
649     UserOperation::GetBannedPersons => {
650       do_websocket_operation::<GetBannedPersons>(context, id, op, data).await
651     }
652     UserOperation::BlockPerson => {
653       do_websocket_operation::<BlockPerson>(context, id, op, data).await
654     }
655     UserOperation::GetPersonMentions => {
656       do_websocket_operation::<GetPersonMentions>(context, id, op, data).await
657     }
658     UserOperation::MarkPersonMentionAsRead => {
659       do_websocket_operation::<MarkPersonMentionAsRead>(context, id, op, data).await
660     }
661     UserOperation::MarkCommentReplyAsRead => {
662       do_websocket_operation::<MarkCommentReplyAsRead>(context, id, op, data).await
663     }
664     UserOperation::MarkAllAsRead => {
665       do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
666     }
667     UserOperation::PasswordReset => {
668       do_websocket_operation::<PasswordReset>(context, id, op, data).await
669     }
670     UserOperation::PasswordChange => {
671       do_websocket_operation::<PasswordChangeAfterReset>(context, id, op, data).await
672     }
673     UserOperation::UserJoin => do_websocket_operation::<UserJoin>(context, id, op, data).await,
674     UserOperation::PostJoin => do_websocket_operation::<PostJoin>(context, id, op, data).await,
675     UserOperation::CommunityJoin => {
676       do_websocket_operation::<CommunityJoin>(context, id, op, data).await
677     }
678     UserOperation::ModJoin => do_websocket_operation::<ModJoin>(context, id, op, data).await,
679     UserOperation::SaveUserSettings => {
680       do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
681     }
682     UserOperation::ChangePassword => {
683       do_websocket_operation::<ChangePassword>(context, id, op, data).await
684     }
685     UserOperation::GetReportCount => {
686       do_websocket_operation::<GetReportCount>(context, id, op, data).await
687     }
688     UserOperation::GetUnreadCount => {
689       do_websocket_operation::<GetUnreadCount>(context, id, op, data).await
690     }
691     UserOperation::VerifyEmail => {
692       do_websocket_operation::<VerifyEmail>(context, id, op, data).await
693     }
694
695     // Private Message ops
696     UserOperation::MarkPrivateMessageAsRead => {
697       do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
698     }
699     UserOperation::CreatePrivateMessageReport => {
700       do_websocket_operation::<CreatePrivateMessageReport>(context, id, op, data).await
701     }
702     UserOperation::ResolvePrivateMessageReport => {
703       do_websocket_operation::<ResolvePrivateMessageReport>(context, id, op, data).await
704     }
705     UserOperation::ListPrivateMessageReports => {
706       do_websocket_operation::<ListPrivateMessageReports>(context, id, op, data).await
707     }
708
709     // Site ops
710     UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
711     UserOperation::PurgePerson => {
712       do_websocket_operation::<PurgePerson>(context, id, op, data).await
713     }
714     UserOperation::PurgeCommunity => {
715       do_websocket_operation::<PurgeCommunity>(context, id, op, data).await
716     }
717     UserOperation::PurgePost => do_websocket_operation::<PurgePost>(context, id, op, data).await,
718     UserOperation::PurgeComment => {
719       do_websocket_operation::<PurgeComment>(context, id, op, data).await
720     }
721     UserOperation::TransferCommunity => {
722       do_websocket_operation::<TransferCommunity>(context, id, op, data).await
723     }
724     UserOperation::LeaveAdmin => do_websocket_operation::<LeaveAdmin>(context, id, op, data).await,
725
726     // Community ops
727     UserOperation::FollowCommunity => {
728       do_websocket_operation::<FollowCommunity>(context, id, op, data).await
729     }
730     UserOperation::BlockCommunity => {
731       do_websocket_operation::<BlockCommunity>(context, id, op, data).await
732     }
733     UserOperation::BanFromCommunity => {
734       do_websocket_operation::<BanFromCommunity>(context, id, op, data).await
735     }
736     UserOperation::AddModToCommunity => {
737       do_websocket_operation::<AddModToCommunity>(context, id, op, data).await
738     }
739
740     // Post ops
741     UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await,
742     UserOperation::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await,
743     UserOperation::CreatePostLike => {
744       do_websocket_operation::<CreatePostLike>(context, id, op, data).await
745     }
746     UserOperation::MarkPostAsRead => {
747       do_websocket_operation::<MarkPostAsRead>(context, id, op, data).await
748     }
749     UserOperation::SavePost => do_websocket_operation::<SavePost>(context, id, op, data).await,
750     UserOperation::CreatePostReport => {
751       do_websocket_operation::<CreatePostReport>(context, id, op, data).await
752     }
753     UserOperation::ListPostReports => {
754       do_websocket_operation::<ListPostReports>(context, id, op, data).await
755     }
756     UserOperation::ResolvePostReport => {
757       do_websocket_operation::<ResolvePostReport>(context, id, op, data).await
758     }
759     UserOperation::GetSiteMetadata => {
760       do_websocket_operation::<GetSiteMetadata>(context, id, op, data).await
761     }
762
763     // Comment ops
764     UserOperation::SaveComment => {
765       do_websocket_operation::<SaveComment>(context, id, op, data).await
766     }
767     UserOperation::CreateCommentLike => {
768       do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
769     }
770     UserOperation::CreateCommentReport => {
771       do_websocket_operation::<CreateCommentReport>(context, id, op, data).await
772     }
773     UserOperation::ListCommentReports => {
774       do_websocket_operation::<ListCommentReports>(context, id, op, data).await
775     }
776     UserOperation::ResolveCommentReport => {
777       do_websocket_operation::<ResolveCommentReport>(context, id, op, data).await
778     }
779   }
780 }
781
782 async fn do_websocket_operation<'a, 'b, Data>(
783   context: LemmyContext,
784   id: ConnectionId,
785   op: UserOperation,
786   data: &str,
787 ) -> result::Result<String, LemmyError>
788 where
789   Data: Perform + SendActivity<Response = <Data as Perform>::Response>,
790   for<'de> Data: Deserialize<'de>,
791 {
792   let parsed_data: Data = serde_json::from_str(data)?;
793   let res = parsed_data
794     .perform(&web::Data::new(context.clone()), Some(id))
795     .await?;
796   SendActivity::send_activity(&parsed_data, &res, &context).await?;
797   serialize_websocket_message(&op, &res)
798 }