1use crate::database::Database;
2use crate::model::{DatabaseError, ResponseCreate, ResponseEdit, ResponseEditTags, ResponseEditContext};
3use crate::routing::pages::PaginatedQuery;
4use axum::extract::Query;
5use axum::http::{HeaderMap, HeaderValue};
6use hcaptcha_no_wasm::Hcaptcha;
7use authbeam::model::NotificationCreate;
8use databeam::prelude::DefaultReturn;
9
10use axum::response::{IntoResponse, Redirect};
11use axum::{
12 extract::{Path, State},
13 routing::{delete, get, post},
14 Json, Router,
15};
16
17use axum_extra::extract::cookie::CookieJar;
18use rainbeam::model::{ResponseDeleteMultiple, ResponseEditTagsMultiple, ResponseEditWarning};
19
20pub fn routes(database: Database) -> Router {
21 Router::new()
22 .route("/", post(create_request))
23 .route("/{id}", get(get_request))
24 .route("/{id}", post(edit_request))
25 .route("/{id}/tags", post(edit_tags_request))
26 .route("/{id}/context", post(edit_context_request))
27 .route("/{id}/context/warning", post(edit_warning_request))
28 .route("/{id}", delete(delete_request))
29 .route("/{id}/unsend", post(unsend_request))
30 .route("/{id}/report", post(report_request))
31 .route("/mass/tags", post(edit_tags_multiple_request))
32 .route("/mass/delete", post(delete_multiple_request))
33 .route("/timeline/home", get(home_timeline_request))
35 .with_state(database)
37}
38
39pub async fn create_request(
43 jar: CookieJar,
44 State(database): State<Database>,
45 Json(req): Json<ResponseCreate>,
46) -> impl IntoResponse {
47 let auth_user = match jar.get("__Secure-Token") {
49 Some(c) => match database
50 .auth
51 .get_profile_by_unhashed(c.value_trimmed())
52 .await
53 {
54 Ok(ua) => ua.id,
55 Err(_) => return Json(DatabaseError::NotAllowed.into()),
56 },
57 None => return Json(DatabaseError::NotAllowed.into()),
58 };
59
60 Json(match database.create_response(req, auth_user).await {
62 Ok(r) => DefaultReturn {
63 success: true,
64 message: String::new(),
65 payload: Some(r),
66 },
67 Err(e) => e.into(),
68 })
69}
70
71pub async fn get_request(
73 Path(id): Path<String>,
74 State(database): State<Database>,
75) -> impl IntoResponse {
76 Json(match database.get_response(id).await {
77 Ok(mut r) => DefaultReturn {
78 success: true,
79 message: String::new(),
80 payload: {
81 if r.0.author.id.starts_with("anonymous#") {
83 r.0.author.id = "anonymous".to_string()
84 }
85
86 r.0.author.clean();
88 r.0.recipient.clean();
89 r.1.author.clean();
90
91 Some(r)
93 },
94 },
95 Err(e) => e.into(),
96 })
97}
98
99pub async fn expand_request(
101 Path(id): Path<String>,
102 State(database): State<Database>,
103) -> impl IntoResponse {
104 match database.get_response(id).await {
105 Ok(r) => Redirect::to(&format!("/@{}/r/{}", r.1.author.username, r.1.id)),
106 Err(_) => Redirect::to("/"),
107 }
108}
109
110pub async fn edit_request(
112 jar: CookieJar,
113 Path(id): Path<String>,
114 State(database): State<Database>,
115 Json(req): Json<ResponseEdit>,
116) -> impl IntoResponse {
117 let auth_user = match jar.get("__Secure-Token") {
119 Some(c) => match database
120 .auth
121 .get_profile_by_unhashed(c.value_trimmed())
122 .await
123 {
124 Ok(ua) => ua,
125 Err(_) => {
126 return Json(DatabaseError::NotAllowed.into());
127 }
128 },
129 None => {
130 return Json(DatabaseError::NotAllowed.into());
131 }
132 };
133
134 Json(
136 match database
137 .update_response_content(id, req.content, auth_user)
138 .await
139 {
140 Ok(r) => DefaultReturn {
141 success: true,
142 message: String::new(),
143 payload: Some(r),
144 },
145 Err(e) => e.into(),
146 },
147 )
148}
149
150pub async fn edit_tags_request(
152 jar: CookieJar,
153 Path(id): Path<String>,
154 State(database): State<Database>,
155 Json(req): Json<ResponseEditTags>,
156) -> impl IntoResponse {
157 let auth_user = match jar.get("__Secure-Token") {
159 Some(c) => match database
160 .auth
161 .get_profile_by_unhashed(c.value_trimmed())
162 .await
163 {
164 Ok(ua) => ua,
165 Err(_) => {
166 return Json(DatabaseError::NotAllowed.into());
167 }
168 },
169 None => {
170 return Json(DatabaseError::NotAllowed.into());
171 }
172 };
173
174 Json(
176 match database.update_response_tags(id, req.tags, auth_user).await {
177 Ok(_) => DefaultReturn {
178 success: true,
179 message: "Response updated!".to_string(),
180 payload: (),
181 },
182 Err(e) => e.into(),
183 },
184 )
185}
186
187pub async fn edit_tags_multiple_request(
189 jar: CookieJar,
190 State(database): State<Database>,
191 Json(req): Json<ResponseEditTagsMultiple>,
192) -> impl IntoResponse {
193 let auth_user = match jar.get("__Secure-Token") {
195 Some(c) => match database
196 .auth
197 .get_profile_by_unhashed(c.value_trimmed())
198 .await
199 {
200 Ok(ua) => ua,
201 Err(_) => {
202 return Json(DatabaseError::NotAllowed.into());
203 }
204 },
205 None => {
206 return Json(DatabaseError::NotAllowed.into());
207 }
208 };
209
210 Json(
212 match database
213 .update_response_tags_multiple(req.ids, req.tags, auth_user)
214 .await
215 {
216 Ok(_) => DefaultReturn {
217 success: true,
218 message: "Responses updated!".to_string(),
219 payload: (),
220 },
221 Err(e) => e.into(),
222 },
223 )
224}
225
226pub async fn edit_context_request(
228 jar: CookieJar,
229 Path(id): Path<String>,
230 State(database): State<Database>,
231 Json(req): Json<ResponseEditContext>,
232) -> impl IntoResponse {
233 let auth_user = match jar.get("__Secure-Token") {
235 Some(c) => match database
236 .auth
237 .get_profile_by_unhashed(c.value_trimmed())
238 .await
239 {
240 Ok(ua) => ua,
241 Err(_) => {
242 return Json(DatabaseError::NotAllowed.into());
243 }
244 },
245 None => {
246 return Json(DatabaseError::NotAllowed.into());
247 }
248 };
249
250 Json(
252 match database
253 .update_response_context(id, req.context, auth_user)
254 .await
255 {
256 Ok(r) => DefaultReturn {
257 success: true,
258 message: String::new(),
259 payload: Some(r),
260 },
261 Err(e) => e.into(),
262 },
263 )
264}
265
266pub async fn edit_warning_request(
268 jar: CookieJar,
269 Path(id): Path<String>,
270 State(database): State<Database>,
271 Json(req): Json<ResponseEditWarning>,
272) -> impl IntoResponse {
273 let auth_user = match jar.get("__Secure-Token") {
275 Some(c) => match database
276 .auth
277 .get_profile_by_unhashed(c.value_trimmed())
278 .await
279 {
280 Ok(ua) => ua,
281 Err(_) => {
282 return Json(DatabaseError::NotAllowed.into());
283 }
284 },
285 None => {
286 return Json(DatabaseError::NotAllowed.into());
287 }
288 };
289
290 let response = match database.get_response_short(id.clone()).await {
292 Ok(q) => q,
293 Err(e) => return Json(e.into()),
294 };
295
296 let mut context = response.context;
297 context.warning = req.warning;
298
299 Json(
301 match database
302 .update_response_context(id, context, auth_user)
303 .await
304 {
305 Ok(r) => DefaultReturn {
306 success: true,
307 message: String::new(),
308 payload: Some(r),
309 },
310 Err(e) => e.into(),
311 },
312 )
313}
314
315pub async fn delete_request(
317 jar: CookieJar,
318 Path(id): Path<String>,
319 State(database): State<Database>,
320) -> impl IntoResponse {
321 let auth_user = match jar.get("__Secure-Token") {
323 Some(c) => match database
324 .auth
325 .get_profile_by_unhashed(c.value_trimmed())
326 .await
327 {
328 Ok(ua) => ua,
329 Err(_) => {
330 return Json(DatabaseError::NotAllowed.into());
331 }
332 },
333 None => {
334 return Json(DatabaseError::NotAllowed.into());
335 }
336 };
337
338 Json(match database.delete_response(id, auth_user, false).await {
340 Ok(r) => DefaultReturn {
341 success: true,
342 message: String::new(),
343 payload: Some(r),
344 },
345 Err(e) => e.into(),
346 })
347}
348
349pub async fn delete_multiple_request(
351 jar: CookieJar,
352 State(database): State<Database>,
353 Json(req): Json<ResponseDeleteMultiple>,
354) -> impl IntoResponse {
355 let auth_user = match jar.get("__Secure-Token") {
357 Some(c) => match database
358 .auth
359 .get_profile_by_unhashed(c.value_trimmed())
360 .await
361 {
362 Ok(ua) => ua,
363 Err(_) => {
364 return Json(DatabaseError::NotAllowed.into());
365 }
366 },
367 None => {
368 return Json(DatabaseError::NotAllowed.into());
369 }
370 };
371
372 Json(
374 match database.delete_response_multiple(req.ids, auth_user).await {
375 Ok(_) => DefaultReturn {
376 success: true,
377 message: "Responses delete!".to_string(),
378 payload: (),
379 },
380 Err(e) => e.into(),
381 },
382 )
383}
384
385pub async fn unsend_request(
387 jar: CookieJar,
388 Path(id): Path<String>,
389 State(database): State<Database>,
390) -> impl IntoResponse {
391 let auth_user = match jar.get("__Secure-Token") {
393 Some(c) => match database
394 .auth
395 .get_profile_by_unhashed(c.value_trimmed())
396 .await
397 {
398 Ok(ua) => ua,
399 Err(_) => {
400 return Json(DatabaseError::NotAllowed.into());
401 }
402 },
403 None => {
404 return Json(DatabaseError::NotAllowed.into());
405 }
406 };
407
408 Json(match database.unsend_response(id, auth_user).await {
410 Ok(r) => DefaultReturn {
411 success: true,
412 message: String::new(),
413 payload: Some(r),
414 },
415 Err(e) => e.into(),
416 })
417}
418
419pub async fn report_request(
421 headers: HeaderMap,
422 Path(id): Path<String>,
423 State(database): State<Database>,
424 Json(req): Json<super::CreateReport>,
425) -> impl IntoResponse {
426 if let Err(e) = req
428 .valid_response(&database.config.captcha.secret, None)
429 .await
430 {
431 return Json(DefaultReturn {
432 success: false,
433 message: e.to_string(),
434 payload: (),
435 });
436 }
437
438 if let Err(_) = database.get_response(id.clone()).await {
440 return Json(DefaultReturn {
441 success: false,
442 message: DatabaseError::NotFound.to_string(),
443 payload: (),
444 });
445 };
446
447 let real_ip = if let Some(ref real_ip_header) = database.config.real_ip_header {
449 headers
450 .get(real_ip_header.to_owned())
451 .unwrap_or(&HeaderValue::from_static(""))
452 .to_str()
453 .unwrap_or("")
454 .to_string()
455 } else {
456 String::new()
457 };
458
459 if database.auth.get_ipban_by_ip(&real_ip).await.is_ok() {
461 return Json(DefaultReturn {
462 success: false,
463 message: DatabaseError::Banned.to_string(),
464 payload: (),
465 });
466 }
467
468 match database
470 .auth
471 .create_notification(
472 NotificationCreate {
473 title: format!("**RESPONSE REPORT**: {id}"),
474 content: format!("{}\n\n***\n\n[{real_ip}](/+i/{real_ip})", req.content),
475 address: format!("/response/{id}"),
476 recipient: "*".to_string(), },
478 None,
479 )
480 .await
481 {
482 Ok(_) => {
483 return Json(DefaultReturn {
484 success: true,
485 message: "Response reported!".to_string(),
486 payload: (),
487 })
488 }
489 Err(_) => Json(DefaultReturn {
490 success: false,
491 message: DatabaseError::NotFound.to_string(),
492 payload: (),
493 }),
494 }
495}
496
497pub async fn home_timeline_request(
499 jar: CookieJar,
500 State(database): State<Database>,
501 Query(props): Query<PaginatedQuery>,
502) -> impl IntoResponse {
503 let auth_user = match jar.get("__Secure-Token") {
505 Some(c) => match database
506 .auth
507 .get_profile_by_unhashed(c.value_trimmed())
508 .await
509 {
510 Ok(ua) => ua,
511 Err(_) => {
512 return Json(DatabaseError::NotAllowed.into());
513 }
514 },
515 None => {
516 return Json(DatabaseError::NotAllowed.into());
517 }
518 };
519
520 Json(
522 match database
523 .get_responses_by_following_paginated(&auth_user.id, props.page)
524 .await
525 {
526 Ok(mut r) => {
527 for response in &mut r {
528 response.1.author.clean();
529 response.0.recipient.clean();
530 response.0.author.clean();
531 }
532
533 DefaultReturn {
534 success: true,
535 message: String::new(),
536 payload: Some(r),
537 }
538 }
539 Err(e) => e.into(),
540 },
541 )
542}