]> Untitled Git - lemmy.git/blob - crates/api/src/lib.rs
Adding GetUnreadCount to the API. Fixes #1794 (#1842)
[lemmy.git] / crates / api / src / lib.rs
1 use actix_web::{web, web::Data};
2 use captcha::Captcha;
3 use lemmy_api_common::{comment::*, community::*, person::*, post::*, site::*, websocket::*};
4 use lemmy_utils::{ConnectionId, LemmyError};
5 use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
6 use serde::Deserialize;
7
8 mod comment;
9 mod comment_report;
10 mod community;
11 mod local_user;
12 mod post;
13 mod post_report;
14 mod private_message;
15 mod site;
16 mod websocket;
17
18 #[async_trait::async_trait(?Send)]
19 pub trait Perform {
20   type Response: serde::ser::Serialize + Send;
21
22   async fn perform(
23     &self,
24     context: &Data<LemmyContext>,
25     websocket_id: Option<ConnectionId>,
26   ) -> Result<Self::Response, LemmyError>;
27 }
28
29 pub async fn match_websocket_operation(
30   context: LemmyContext,
31   id: ConnectionId,
32   op: UserOperation,
33   data: &str,
34 ) -> Result<String, LemmyError> {
35   match op {
36     // User ops
37     UserOperation::Login => do_websocket_operation::<Login>(context, id, op, data).await,
38     UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
39     UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
40     UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
41     UserOperation::BanPerson => do_websocket_operation::<BanPerson>(context, id, op, data).await,
42     UserOperation::BlockPerson => {
43       do_websocket_operation::<BlockPerson>(context, id, op, data).await
44     }
45     UserOperation::GetPersonMentions => {
46       do_websocket_operation::<GetPersonMentions>(context, id, op, data).await
47     }
48     UserOperation::MarkPersonMentionAsRead => {
49       do_websocket_operation::<MarkPersonMentionAsRead>(context, id, op, data).await
50     }
51     UserOperation::MarkAllAsRead => {
52       do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
53     }
54     UserOperation::PasswordReset => {
55       do_websocket_operation::<PasswordReset>(context, id, op, data).await
56     }
57     UserOperation::PasswordChange => {
58       do_websocket_operation::<PasswordChange>(context, id, op, data).await
59     }
60     UserOperation::UserJoin => do_websocket_operation::<UserJoin>(context, id, op, data).await,
61     UserOperation::PostJoin => do_websocket_operation::<PostJoin>(context, id, op, data).await,
62     UserOperation::CommunityJoin => {
63       do_websocket_operation::<CommunityJoin>(context, id, op, data).await
64     }
65     UserOperation::ModJoin => do_websocket_operation::<ModJoin>(context, id, op, data).await,
66     UserOperation::SaveUserSettings => {
67       do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
68     }
69     UserOperation::ChangePassword => {
70       do_websocket_operation::<ChangePassword>(context, id, op, data).await
71     }
72     UserOperation::GetReportCount => {
73       do_websocket_operation::<GetReportCount>(context, id, op, data).await
74     }
75     UserOperation::GetUnreadCount => {
76       do_websocket_operation::<GetUnreadCount>(context, id, op, data).await
77     }
78
79     // Private Message ops
80     UserOperation::MarkPrivateMessageAsRead => {
81       do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
82     }
83
84     // Site ops
85     UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
86     UserOperation::GetSiteConfig => {
87       do_websocket_operation::<GetSiteConfig>(context, id, op, data).await
88     }
89     UserOperation::SaveSiteConfig => {
90       do_websocket_operation::<SaveSiteConfig>(context, id, op, data).await
91     }
92     UserOperation::Search => do_websocket_operation::<Search>(context, id, op, data).await,
93     UserOperation::ResolveObject => {
94       do_websocket_operation::<ResolveObject>(context, id, op, data).await
95     }
96     UserOperation::TransferCommunity => {
97       do_websocket_operation::<TransferCommunity>(context, id, op, data).await
98     }
99     UserOperation::TransferSite => {
100       do_websocket_operation::<TransferSite>(context, id, op, data).await
101     }
102
103     // Community ops
104     UserOperation::FollowCommunity => {
105       do_websocket_operation::<FollowCommunity>(context, id, op, data).await
106     }
107     UserOperation::BlockCommunity => {
108       do_websocket_operation::<BlockCommunity>(context, id, op, data).await
109     }
110     UserOperation::BanFromCommunity => {
111       do_websocket_operation::<BanFromCommunity>(context, id, op, data).await
112     }
113     UserOperation::AddModToCommunity => {
114       do_websocket_operation::<AddModToCommunity>(context, id, op, data).await
115     }
116
117     // Post ops
118     UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await,
119     UserOperation::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await,
120     UserOperation::CreatePostLike => {
121       do_websocket_operation::<CreatePostLike>(context, id, op, data).await
122     }
123     UserOperation::SavePost => do_websocket_operation::<SavePost>(context, id, op, data).await,
124     UserOperation::CreatePostReport => {
125       do_websocket_operation::<CreatePostReport>(context, id, op, data).await
126     }
127     UserOperation::ListPostReports => {
128       do_websocket_operation::<ListPostReports>(context, id, op, data).await
129     }
130     UserOperation::ResolvePostReport => {
131       do_websocket_operation::<ResolvePostReport>(context, id, op, data).await
132     }
133     UserOperation::GetSiteMetadata => {
134       do_websocket_operation::<GetSiteMetadata>(context, id, op, data).await
135     }
136
137     // Comment ops
138     UserOperation::MarkCommentAsRead => {
139       do_websocket_operation::<MarkCommentAsRead>(context, id, op, data).await
140     }
141     UserOperation::SaveComment => {
142       do_websocket_operation::<SaveComment>(context, id, op, data).await
143     }
144     UserOperation::CreateCommentLike => {
145       do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
146     }
147     UserOperation::CreateCommentReport => {
148       do_websocket_operation::<CreateCommentReport>(context, id, op, data).await
149     }
150     UserOperation::ListCommentReports => {
151       do_websocket_operation::<ListCommentReports>(context, id, op, data).await
152     }
153     UserOperation::ResolveCommentReport => {
154       do_websocket_operation::<ResolveCommentReport>(context, id, op, data).await
155     }
156   }
157 }
158
159 async fn do_websocket_operation<'a, 'b, Data>(
160   context: LemmyContext,
161   id: ConnectionId,
162   op: UserOperation,
163   data: &str,
164 ) -> Result<String, LemmyError>
165 where
166   for<'de> Data: Deserialize<'de> + 'a,
167   Data: Perform,
168 {
169   let parsed_data: Data = serde_json::from_str(data)?;
170   let res = parsed_data
171     .perform(&web::Data::new(context), Some(id))
172     .await?;
173   serialize_websocket_message(&op, &res)
174 }
175
176 /// Converts the captcha to a base64 encoded wav audio file
177 pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> String {
178   let letters = captcha.as_wav();
179
180   let mut concat_letters: Vec<u8> = Vec::new();
181
182   for letter in letters {
183     let bytes = letter.unwrap_or_default();
184     concat_letters.extend(bytes);
185   }
186
187   // Convert to base64
188   base64::encode(concat_letters)
189 }
190
191 #[cfg(test)]
192 mod tests {
193   use lemmy_api_common::check_validator_time;
194   use lemmy_db_queries::{
195     establish_unpooled_connection,
196     source::{local_user::LocalUser_, secret::Secret_},
197     Crud,
198   };
199   use lemmy_db_schema::source::{
200     local_user::{LocalUser, LocalUserForm},
201     person::{Person, PersonForm},
202     secret::Secret,
203   };
204   use lemmy_utils::{claims::Claims, settings::structs::Settings};
205
206   #[test]
207   fn test_should_not_validate_user_token_after_password_change() {
208     let conn = establish_unpooled_connection();
209     let secret = Secret::init(&conn).unwrap();
210     let settings = Settings::init().unwrap();
211
212     let new_person = PersonForm {
213       name: "Gerry9812".into(),
214       ..PersonForm::default()
215     };
216
217     let inserted_person = Person::create(&conn, &new_person).unwrap();
218
219     let local_user_form = LocalUserForm {
220       person_id: inserted_person.id,
221       password_encrypted: "123456".to_string(),
222       ..LocalUserForm::default()
223     };
224
225     let inserted_local_user = LocalUser::create(&conn, &local_user_form).unwrap();
226
227     let jwt = Claims::jwt(
228       inserted_local_user.id.0,
229       &secret.jwt_secret,
230       &settings.hostname,
231     )
232     .unwrap();
233     let claims = Claims::decode(&jwt, &secret.jwt_secret).unwrap().claims;
234     let check = check_validator_time(&inserted_local_user.validator_time, &claims);
235     assert!(check.is_ok());
236
237     // The check should fail, since the validator time is now newer than the jwt issue time
238     let updated_local_user =
239       LocalUser::update_password(&conn, inserted_local_user.id, "password111").unwrap();
240     let check_after = check_validator_time(&updated_local_user.validator_time, &claims);
241     assert!(check_after.is_err());
242
243     let num_deleted = Person::delete(&conn, inserted_person.id).unwrap();
244     assert_eq!(1, num_deleted);
245   }
246 }