1use std::collections::HashMap;
2
3use authbeam::layout::LayoutComponent;
4use reva_axum::Template;
5use axum::extract::{Path, Query};
6use axum::response::IntoResponse;
7use axum::{extract::State, response::Html};
8use axum_extra::extract::CookieJar;
9
10use authbeam::model::{FinePermission, ItemType, Profile, UserFollow, Warning};
11use serde::Deserialize;
12
13use crate::config::Config;
14use crate::database::Database;
15use crate::model::{DatabaseError, FullResponse, Question, RelationshipStatus};
16use crate::ToHtml;
17
18use super::{clean_metadata, MarkdownTemplate, PaginatedQuery, PasswordQuery, ProfileQuery};
19
20#[derive(Template)]
21#[template(path = "profile/profile.html")]
22struct ProfileTemplate {
23 config: Config,
24 lang: langbeam::LangFile,
25 profile: Option<Box<Profile>>,
26 unread: usize,
27 notifs: usize,
28 other: Box<Profile>,
29 response_count: usize,
30 questions_count: usize,
31 followers_count: usize,
32 following_count: usize,
33 friends_count: usize,
34 is_following: bool,
35 is_following_you: bool,
36 metadata: String,
37 pinned: Option<Vec<FullResponse>>,
38 page: i32,
39 tag: String,
40 query: String,
41 relationship: RelationshipStatus,
43 lock_profile: bool,
44 disallow_anonymous: bool,
45 require_account: bool,
46 hide_social: bool,
47 view_password: String,
48 unlocked: bool,
49 is_powerful: bool, is_helper: bool, is_self: bool,
52}
53
54pub async fn profile_request(
56 jar: CookieJar,
57 Path(username): Path<String>,
58 State(database): State<Database>,
59 Query(query): Query<ProfileQuery>,
60) -> impl IntoResponse {
61 let auth_user = match jar.get("__Secure-Token") {
62 Some(c) => match database
63 .auth
64 .get_profile_by_unhashed(c.value_trimmed())
65 .await
66 {
67 Ok(ua) => Some(ua),
68 Err(_) => None,
69 },
70 None => None,
71 };
72
73 let unread = if let Some(ref ua) = auth_user {
74 database.get_inbox_count_by_recipient(&ua.id).await
75 } else {
76 0
77 };
78
79 let notifs = if let Some(ref ua) = auth_user {
80 database
81 .auth
82 .get_notification_count_by_recipient(&ua.id)
83 .await
84 } else {
85 0
86 };
87
88 let other = match database.auth.get_profile(&username).await {
89 Ok(ua) => ua,
90 Err(_) => return Html(DatabaseError::NotFound.to_html(database)),
91 };
92
93 if other.metadata.is_true("rainbeam:authenticated_only") & auth_user.is_none() {
94 return Html(DatabaseError::NotAllowed.to_html(database));
96 }
97
98 if other.id == "0" {
99 return Html(
100 MarkdownTemplate {
101 config: database.config.clone(),
102 lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
103 c.value_trimmed()
104 } else {
105 ""
106 }),
107 profile: auth_user,
108 title: "System".to_string(),
109 text: "Reserved system profile.".to_string(),
110 }
111 .render()
112 .unwrap(),
113 );
114 } else if other.id == "@" {
115 return Html(
116 MarkdownTemplate {
117 config: database.config.clone(),
118 lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
119 c.value_trimmed()
120 } else {
121 ""
122 }),
123 profile: auth_user,
124 title: "Everybody".to_string(),
125 text: "Hello from everyone!".to_string(),
126 }
127 .render()
128 .unwrap(),
129 );
130 }
131
132 let is_following = if let Some(ref ua) = auth_user {
133 match database.auth.get_follow(&ua.id, &other.id).await {
134 Ok(_) => true,
135 Err(_) => false,
136 }
137 } else {
138 false
139 };
140
141 let is_following_you = if let Some(ref ua) = auth_user {
142 match database.auth.get_follow(&other.id, &ua.id).await {
143 Ok(_) => true,
144 Err(_) => false,
145 }
146 } else {
147 false
148 };
149
150 let pinned = if let Some(pinned) = other.metadata.kv.get("sparkler:pinned") {
152 if pinned.is_empty() {
153 None
154 } else {
155 let mut out = Vec::new();
156
157 for id in pinned.split(",") {
158 match database.get_response(id.to_string()).await {
159 Ok(response) => {
160 if response.1.author.id != other.id {
161 continue;
163 }
164
165 out.push(response)
167 }
168 Err(_) => continue,
169 }
170 }
171
172 Some(out)
173 }
174 } else {
175 None
176 };
177
178 let mut is_helper: bool = false;
179 let is_powerful = if let Some(ref ua) = auth_user {
180 let group = match database.auth.get_group_by_id(ua.group).await {
181 Ok(g) => g,
182 Err(_) => return Html(DatabaseError::Other.to_html(database)),
183 };
184
185 is_helper = group.permissions.check_helper();
186 group.permissions.check_manager()
187 } else {
188 false
189 };
190
191 let is_self = if let Some(ref profile) = auth_user {
192 profile.id == other.id
193 } else {
194 false
195 };
196
197 let relationship = if is_self | is_helper {
198 RelationshipStatus::Friends
201 } else {
202 if let Some(ref profile) = auth_user {
203 database
204 .auth
205 .get_user_relationship(&other.id, &profile.id)
206 .await
207 .0
208 } else {
209 RelationshipStatus::Unknown
210 }
211 };
212
213 let is_blocked = relationship == RelationshipStatus::Blocked;
214
215 if !is_helper && is_blocked {
216 return Html(DatabaseError::NotFound.to_html(database));
217 }
218
219 Html(
221 ProfileTemplate {
222 config: database.config.clone(),
223 lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
224 c.value_trimmed()
225 } else {
226 ""
227 }),
228 profile: auth_user.clone(),
229 unread,
230 notifs,
231 other: other.clone(),
232 response_count: database.get_response_count_by_author(&other.id).await,
233 questions_count: database
234 .get_global_questions_count_by_author(&other.id)
235 .await,
236 followers_count: database.auth.get_followers_count(&other.id).await,
237 following_count: database.auth.get_following_count(&other.id).await,
238 friends_count: database.auth.get_friendship_count_by_user(&other.id).await,
239 is_following,
240 is_following_you,
241 metadata: clean_metadata(&other.metadata),
242 pinned,
243 page: query.page,
244 tag: query.tag.unwrap_or(String::new()),
245 query: query.q.unwrap_or(String::new()),
246 relationship,
248 lock_profile: other
249 .metadata
250 .kv
251 .get("sparkler:lock_profile")
252 .unwrap_or(&"false".to_string())
253 == "true",
254 disallow_anonymous: other
255 .metadata
256 .kv
257 .get("sparkler:disallow_anonymous")
258 .unwrap_or(&"false".to_string())
259 == "true",
260 require_account: other
261 .metadata
262 .kv
263 .get("sparkler:require_account")
264 .unwrap_or(&"false".to_string())
265 == "true",
266 hide_social: (other
267 .metadata
268 .kv
269 .get("sparkler:private_social")
270 .unwrap_or(&"false".to_string())
271 == "true")
272 && !is_self,
273 view_password: query.password.clone(),
274 unlocked: if other.metadata.exists("rainbeam:view_password") {
275 (other.metadata.soft_get("rainbeam:view_password") == query.password)
276 | is_self
277 | is_helper
278 } else {
279 true
280 },
281 is_powerful,
282 is_helper,
283 is_self,
284 }
285 .render()
286 .unwrap(),
287 )
288}
289
290#[derive(Template)]
291#[template(path = "partials/profile/feed.html")]
292struct PartialProfileTemplate {
293 config: Config,
294 lang: langbeam::LangFile,
295 profile: Option<Box<Profile>>,
296 other: Box<Profile>,
297 responses: Vec<FullResponse>,
298 relationships: HashMap<String, RelationshipStatus>,
299 is_powerful: bool, is_helper: bool, }
303
304pub async fn partial_profile_request(
306 jar: CookieJar,
307 Path(username): Path<String>,
308 State(database): State<Database>,
309 Query(query): Query<ProfileQuery>,
310) -> impl IntoResponse {
311 let auth_user = match jar.get("__Secure-Token") {
312 Some(c) => match database
313 .auth
314 .get_profile_by_unhashed(c.value_trimmed())
315 .await
316 {
317 Ok(ua) => Some(ua),
318 Err(_) => None,
319 },
320 None => None,
321 };
322
323 let other = match database.auth.get_profile_by_username(&username).await {
324 Ok(ua) => ua,
325 Err(_) => return Html(DatabaseError::NotFound.to_html(database)),
326 };
327
328 let responses = if let Some(ref tag) = query.tag {
329 match database
331 .get_responses_by_author_tagged_paginated(
332 other.id.to_owned(),
333 tag.to_owned(),
334 query.page,
335 )
336 .await
337 {
338 Ok(responses) => responses,
339 Err(e) => return Html(e.to_html(database)),
340 }
341 } else {
342 if let Some(ref search) = query.q {
343 match database
345 .get_responses_by_author_searched_paginated(
346 other.id.to_owned(),
347 search.to_owned(),
348 query.page,
349 )
350 .await
351 {
352 Ok(responses) => responses,
353 Err(e) => return Html(e.to_html(database)),
354 }
355 } else {
356 match database
358 .get_responses_by_author_paginated(other.id.to_owned(), query.page)
359 .await
360 {
361 Ok(responses) => responses,
362 Err(e) => return Html(e.to_html(database)),
363 }
364 }
365 };
366
367 let mut is_helper: bool = false;
368 let is_powerful = if let Some(ref ua) = auth_user {
369 let group = match database.auth.get_group_by_id(ua.group).await {
370 Ok(g) => g,
371 Err(_) => return Html(DatabaseError::Other.to_html(database)),
372 };
373
374 is_helper = group.permissions.check_helper();
375 group.permissions.check_manager()
376 } else {
377 false
378 };
379
380 let is_self = if let Some(ref profile) = auth_user {
381 profile.id == other.id
382 } else {
383 false
384 };
385
386 let relationship = if is_self | is_helper {
387 RelationshipStatus::Friends
390 } else {
391 if let Some(ref profile) = auth_user {
392 database
393 .auth
394 .get_user_relationship(&other.id, &profile.id)
395 .await
396 .0
397 } else {
398 RelationshipStatus::Unknown
399 }
400 };
401
402 let is_blocked = relationship == RelationshipStatus::Blocked;
403
404 if !is_helper && is_blocked {
405 return Html(DatabaseError::NotFound.to_html(database));
406 }
407
408 let mut relationships: HashMap<String, RelationshipStatus> = HashMap::new();
410
411 if let Some(ref ua) = auth_user {
412 for response in &responses {
413 if relationships.contains_key(&response.1.author.id) {
414 continue;
415 }
416
417 if is_helper {
418 relationships.insert(response.1.author.id.clone(), RelationshipStatus::Friends);
420 continue;
421 }
422
423 if response.1.author.id == ua.id {
424 relationships.insert(response.1.author.id.clone(), RelationshipStatus::Friends);
426 continue;
427 };
428
429 relationships.insert(
430 response.1.author.id.clone(),
431 database
432 .auth
433 .get_user_relationship(&response.1.author.id, &ua.id)
434 .await
435 .0,
436 );
437 }
438 } else {
439 for response in &responses {
440 if relationships.contains_key(&response.1.author.id) {
442 continue;
443 }
444
445 relationships.insert(response.1.author.id.clone(), RelationshipStatus::Unknown);
446 }
447 }
448
449 Html(
451 PartialProfileTemplate {
452 config: database.config.clone(),
453 lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
454 c.value_trimmed()
455 } else {
456 ""
457 }),
458 profile: auth_user.clone(),
459 other: other.clone(),
460 responses,
461 relationships,
462 is_powerful,
464 is_helper,
465 }
466 .render()
467 .unwrap(),
468 )
469}
470
471#[derive(Template)]
472#[template(path = "profile/layout_editor.html")]
473struct ProfileLayoutEditorTemplate {
474 config: Config,
475 lang: langbeam::LangFile,
476 profile: Option<Box<Profile>>,
477 other: Box<Profile>,
478 layout: LayoutComponent,
479 is_self: bool,
480}
481
482#[derive(Deserialize)]
483pub struct ProfileLayoutEditorQuery {
484 #[serde(default)]
485 id: String,
486}
487
488pub async fn profile_layout_editor_request(
490 jar: CookieJar,
491 Path(username): Path<String>,
492 State(database): State<Database>,
493 Query(props): Query<ProfileLayoutEditorQuery>,
494) -> impl IntoResponse {
495 let auth_user = match jar.get("__Secure-Token") {
496 Some(c) => match database
497 .auth
498 .get_profile_by_unhashed(c.value_trimmed())
499 .await
500 {
501 Ok(ua) => Some(ua),
502 Err(_) => None,
503 },
504 None => None,
505 };
506
507 let other = match database.auth.get_profile(&username).await {
508 Ok(ua) => ua,
509 Err(_) => return Html(DatabaseError::NotFound.to_html(database)),
510 };
511
512 let is_helper = if let Some(ref ua) = auth_user {
514 let group = match database.auth.get_group_by_id(ua.group).await {
515 Ok(g) => g,
516 Err(_) => return Html(DatabaseError::Other.to_html(database)),
517 };
518
519 group.permissions.check_helper()
520 } else {
521 false
522 };
523
524 let is_self = if let Some(ref profile) = auth_user {
525 profile.id == other.id
526 } else {
527 false
528 };
529
530 if !is_helper && !is_self {
531 return Html(DatabaseError::NotAllowed.to_html(database));
532 }
533
534 Html(
536 ProfileLayoutEditorTemplate {
537 config: database.config.clone(),
538 lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
539 c.value_trimmed()
540 } else {
541 ""
542 }),
543 profile: auth_user.clone(),
544 other: other.clone(),
545 layout: if props.id.is_empty() {
546 other.layout.clone()
547 } else {
548 let item = match database.auth.get_item(&props.id).await {
550 Ok(i) => i,
551 Err(e) => return Html(e.to_string()),
552 };
553
554 if item.r#type != ItemType::Layout {
555 return Html(DatabaseError::ValueError.to_string());
556 }
557
558 if !database
559 .auth
560 .get_transaction_by_customer_item(&auth_user.unwrap().id, &item.id)
561 .await
562 .is_ok()
563 {
564 return Html(DatabaseError::NotAllowed.to_string());
566 }
567
568 let mut layout: LayoutComponent = match serde_json::from_str(&item.content) {
569 Ok(l) => l,
570 Err(_) => return Html(DatabaseError::ValueError.to_string()),
571 };
572
573 layout
575 .options
576 .insert("#rainbeam:market_id".to_string(), props.id.clone());
577
578 layout
580 },
581 is_self,
582 }
583 .render()
584 .unwrap(),
585 )
586}
587
588#[derive(Template)]
589#[template(path = "profile/embed.html")]
590struct ProfileEmbedTemplate {
591 config: Config,
592 lang: langbeam::LangFile,
593 profile: Option<Box<Profile>>,
594 other: Box<Profile>,
595 pinned: Option<Vec<FullResponse>>,
596 is_powerful: bool,
597 is_helper: bool,
598 lock_profile: bool,
599 disallow_anonymous: bool,
600 require_account: bool,
601}
602
603pub async fn profile_embed_request(
605 jar: CookieJar,
606 Path(username): Path<String>,
607 State(database): State<Database>,
608) -> impl IntoResponse {
609 let auth_user = match jar.get("__Secure-Token") {
610 Some(c) => match database
611 .auth
612 .get_profile_by_unhashed(c.value_trimmed())
613 .await
614 {
615 Ok(ua) => Some(ua),
616 Err(_) => None,
617 },
618 None => None,
619 };
620
621 let other = match database.auth.get_profile_by_username(&username).await {
622 Ok(ua) => ua,
623 Err(_) => return Html(DatabaseError::NotFound.to_html(database)),
624 };
625
626 if other.metadata.is_true("rainbeam:authenticated_only") & auth_user.is_none() {
627 return Html(DatabaseError::NotAllowed.to_html(database));
629 }
630
631 let pinned = if let Some(pinned) = other.metadata.kv.get("sparkler:pinned") {
632 if pinned.is_empty() {
633 None
634 } else {
635 let mut out = Vec::new();
636
637 for id in pinned.split(",") {
638 match database.get_response(id.to_string()).await {
639 Ok(response) => {
640 if response.1.author.id != other.id {
641 continue;
643 }
644
645 out.push(response)
647 }
648 Err(_) => continue,
649 }
650 }
651
652 Some(out)
653 }
654 } else {
655 None
656 };
657
658 let lock_profile = other
660 .metadata
661 .kv
662 .get("sparkler:lock_profile")
663 .unwrap_or(&"false".to_string())
664 == "true";
665
666 let disallow_anonymous = other
667 .metadata
668 .kv
669 .get("sparkler:disallow_anonymous")
670 .unwrap_or(&"false".to_string())
671 == "true";
672
673 let require_account = other
674 .metadata
675 .kv
676 .get("sparkler:require_account")
677 .unwrap_or(&"false".to_string())
678 == "true";
679
680 let mut is_helper: bool = false;
681 let is_powerful = if let Some(ref ua) = auth_user {
682 let group = match database.auth.get_group_by_id(ua.group).await {
683 Ok(g) => g,
684 Err(_) => return Html(DatabaseError::Other.to_html(database)),
685 };
686
687 is_helper = group.permissions.check_helper();
688 group.permissions.check_manager()
689 } else {
690 false
691 };
692
693 let relationship = if let Some(ref profile) = auth_user {
694 database
695 .auth
696 .get_user_relationship(&other.id, &profile.id)
697 .await
698 .0
699 } else {
700 RelationshipStatus::Unknown
701 };
702
703 let is_blocked = relationship == RelationshipStatus::Blocked;
704
705 if !is_helper && is_blocked {
706 return Html(DatabaseError::NotFound.to_html(database));
707 }
708
709 Html(
711 ProfileEmbedTemplate {
712 config: database.config.clone(),
713 lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
714 c.value_trimmed()
715 } else {
716 ""
717 }),
718 profile: auth_user.clone(),
719 other: other.clone(),
720 pinned,
721 is_powerful,
722 is_helper,
723 lock_profile,
724 disallow_anonymous,
725 require_account,
726 }
727 .render()
728 .unwrap(),
729 )
730}
731
732#[derive(Template)]
733#[template(path = "profile/social/followers.html")]
734struct FollowersTemplate {
735 config: Config,
736 lang: langbeam::LangFile,
737 profile: Option<Box<Profile>>,
738 unread: usize,
739 notifs: usize,
740 other: Box<Profile>,
741 followers: Vec<(UserFollow, Box<Profile>, Box<Profile>)>,
742 followers_count: usize,
743 following_count: usize,
744 friends_count: usize,
745 page: i32,
746 is_self: bool,
748 is_helper: bool,
749}
750
751pub async fn followers_request(
753 jar: CookieJar,
754 Path(username): Path<String>,
755 State(database): State<Database>,
756 Query(query): Query<PaginatedQuery>,
757) -> impl IntoResponse {
758 let auth_user = match jar.get("__Secure-Token") {
759 Some(c) => match database
760 .auth
761 .get_profile_by_unhashed(c.value_trimmed())
762 .await
763 {
764 Ok(ua) => Some(ua),
765 Err(_) => None,
766 },
767 None => None,
768 };
769
770 let unread = if let Some(ref ua) = auth_user {
771 database.get_inbox_count_by_recipient(&ua.id).await
772 } else {
773 0
774 };
775
776 let notifs = if let Some(ref ua) = auth_user {
777 database
778 .auth
779 .get_notification_count_by_recipient(&ua.id)
780 .await
781 } else {
782 0
783 };
784
785 let other = match database.auth.get_profile_by_username(&username).await {
786 Ok(ua) => ua,
787 Err(e) => return Html(e.to_string()),
788 };
789
790 if other.metadata.is_true("rainbeam:authenticated_only") & auth_user.is_none() {
791 return Html(DatabaseError::NotAllowed.to_html(database));
793 }
794
795 let is_helper = if let Some(ref ua) = auth_user {
796 let group = match database.auth.get_group_by_id(ua.group).await {
797 Ok(g) => g,
798 Err(_) => return Html(DatabaseError::Other.to_html(database)),
799 };
800
801 group.permissions.check_helper()
802 } else {
803 false
804 };
805
806 let is_self = if let Some(ref profile) = auth_user {
807 profile.id == other.id
808 } else {
809 false
810 };
811
812 if !is_self
813 && (other
814 .metadata
815 .kv
816 .get("sparkler:private_social")
817 .unwrap_or(&"false".to_string())
818 == "true")
819 && !is_helper
820 {
821 return Html(DatabaseError::NotAllowed.to_html(database));
823 }
824
825 let relationship = if is_self {
826 RelationshipStatus::Friends
829 } else {
830 if let Some(ref profile) = auth_user {
831 database
832 .auth
833 .get_user_relationship(&other.id, &profile.id)
834 .await
835 .0
836 } else {
837 RelationshipStatus::Unknown
838 }
839 };
840
841 let is_blocked = relationship == RelationshipStatus::Blocked;
842
843 if !is_helper && is_blocked {
844 return Html(DatabaseError::NotFound.to_html(database));
845 }
846
847 Html(
848 FollowersTemplate {
849 config: database.config.clone(),
850 lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
851 c.value_trimmed()
852 } else {
853 ""
854 }),
855 profile: auth_user.clone(),
856 unread,
857 notifs,
858 other: other.clone(),
859 followers: database
860 .auth
861 .get_followers_paginated(&other.id, query.page)
862 .await
863 .unwrap(),
864 followers_count: database.auth.get_followers_count(&other.id).await,
865 following_count: database.auth.get_following_count(&other.id).await,
866 friends_count: database.auth.get_friendship_count_by_user(&other.id).await,
867 page: query.page,
868 is_self,
870 is_helper,
871 }
872 .render()
873 .unwrap(),
874 )
875}
876
877#[derive(Template)]
878#[template(path = "profile/social/following.html")]
879struct FollowingTemplate {
880 config: Config,
881 lang: langbeam::LangFile,
882 profile: Option<Box<Profile>>,
883 unread: usize,
884 notifs: usize,
885 other: Box<Profile>,
886 followers_count: usize,
887 friends_count: usize,
888 following: Vec<(UserFollow, Box<Profile>, Box<Profile>)>,
889 following_count: usize,
890 page: i32,
891 is_self: bool,
893 is_helper: bool,
894}
895
896pub async fn following_request(
898 jar: CookieJar,
899 Path(username): Path<String>,
900 State(database): State<Database>,
901 Query(query): Query<PaginatedQuery>,
902) -> impl IntoResponse {
903 let auth_user = match jar.get("__Secure-Token") {
904 Some(c) => match database
905 .auth
906 .get_profile_by_unhashed(c.value_trimmed())
907 .await
908 {
909 Ok(ua) => Some(ua),
910 Err(_) => None,
911 },
912 None => None,
913 };
914
915 let unread = if let Some(ref ua) = auth_user {
916 database.get_inbox_count_by_recipient(&ua.id).await
917 } else {
918 0
919 };
920
921 let notifs = if let Some(ref ua) = auth_user {
922 database
923 .auth
924 .get_notification_count_by_recipient(&ua.id)
925 .await
926 } else {
927 0
928 };
929
930 let other = match database.auth.get_profile_by_username(&username).await {
931 Ok(ua) => ua,
932 Err(e) => return Html(e.to_string()),
933 };
934
935 if other.metadata.is_true("rainbeam:authenticated_only") & auth_user.is_none() {
936 return Html(DatabaseError::NotAllowed.to_html(database));
938 }
939
940 let is_helper = if let Some(ref ua) = auth_user {
941 let group = match database.auth.get_group_by_id(ua.group).await {
942 Ok(g) => g,
943 Err(_) => return Html(DatabaseError::Other.to_html(database)),
944 };
945
946 group.permissions.check_helper()
947 } else {
948 false
949 };
950
951 let is_self = if let Some(ref profile) = auth_user {
952 profile.id == other.id
953 } else {
954 false
955 };
956
957 if !is_self
958 && (other
959 .metadata
960 .kv
961 .get("sparkler:private_social")
962 .unwrap_or(&"false".to_string())
963 == "true")
964 && !is_helper
965 {
966 return Html(DatabaseError::NotAllowed.to_html(database));
968 }
969
970 let relationship = if is_self {
971 RelationshipStatus::Friends
974 } else {
975 if let Some(ref profile) = auth_user {
976 database
977 .auth
978 .get_user_relationship(&other.id, &profile.id)
979 .await
980 .0
981 } else {
982 RelationshipStatus::Unknown
983 }
984 };
985
986 let is_blocked = relationship == RelationshipStatus::Blocked;
987
988 if !is_helper && is_blocked {
989 return Html(DatabaseError::NotFound.to_html(database));
990 }
991
992 Html(
993 FollowingTemplate {
994 config: database.config.clone(),
995 lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
996 c.value_trimmed()
997 } else {
998 ""
999 }),
1000 profile: auth_user.clone(),
1001 unread,
1002 notifs,
1003 other: other.clone(),
1004 followers_count: database.auth.get_followers_count(&other.id).await,
1005 following_count: database.auth.get_following_count(&other.id).await,
1006 following: database
1007 .auth
1008 .get_following_paginated(&other.id, query.page)
1009 .await
1010 .unwrap(),
1011 friends_count: database.auth.get_friendship_count_by_user(&other.id).await,
1012 page: query.page,
1013 is_self,
1015 is_helper,
1016 }
1017 .render()
1018 .unwrap(),
1019 )
1020}
1021
1022#[derive(Template)]
1023#[template(path = "profile/social/friends.html")]
1024struct FriendsTemplate {
1025 config: Config,
1026 lang: langbeam::LangFile,
1027 profile: Option<Box<Profile>>,
1028 unread: usize,
1029 notifs: usize,
1030 other: Box<Profile>,
1031 friends: Vec<(Box<Profile>, Box<Profile>)>,
1032 followers_count: usize,
1033 following_count: usize,
1034 friends_count: usize,
1035 page: i32,
1036 is_self: bool,
1038 is_helper: bool,
1039}
1040
1041pub async fn friends_request(
1043 jar: CookieJar,
1044 Path(username): Path<String>,
1045 State(database): State<Database>,
1046 Query(query): Query<PaginatedQuery>,
1047) -> impl IntoResponse {
1048 let auth_user = match jar.get("__Secure-Token") {
1049 Some(c) => match database
1050 .auth
1051 .get_profile_by_unhashed(c.value_trimmed())
1052 .await
1053 {
1054 Ok(ua) => Some(ua),
1055 Err(_) => None,
1056 },
1057 None => None,
1058 };
1059
1060 let unread = if let Some(ref ua) = auth_user {
1061 database.get_inbox_count_by_recipient(&ua.id).await
1062 } else {
1063 0
1064 };
1065
1066 let notifs = if let Some(ref ua) = auth_user {
1067 database
1068 .auth
1069 .get_notification_count_by_recipient(&ua.id)
1070 .await
1071 } else {
1072 0
1073 };
1074
1075 let other = match database.auth.get_profile_by_username(&username).await {
1076 Ok(ua) => ua,
1077 Err(e) => return Html(e.to_string()),
1078 };
1079
1080 if other.metadata.is_true("rainbeam:authenticated_only") & auth_user.is_none() {
1081 return Html(DatabaseError::NotAllowed.to_html(database));
1083 }
1084
1085 let is_helper = if let Some(ref ua) = auth_user {
1086 let group = match database.auth.get_group_by_id(ua.group).await {
1087 Ok(g) => g,
1088 Err(_) => return Html(DatabaseError::Other.to_html(database)),
1089 };
1090
1091 group.permissions.check_helper()
1092 } else {
1093 false
1094 };
1095
1096 let is_self = if let Some(ref profile) = auth_user {
1097 profile.id == other.id
1098 } else {
1099 false
1100 };
1101
1102 if !is_self
1103 && (other
1104 .metadata
1105 .kv
1106 .get("sparkler:private_social")
1107 .unwrap_or(&"false".to_string())
1108 == "true")
1109 && !is_helper
1110 {
1111 return Html(DatabaseError::NotAllowed.to_html(database));
1113 }
1114
1115 let relationship = if is_self {
1116 RelationshipStatus::Friends
1119 } else {
1120 if let Some(ref profile) = auth_user {
1121 database
1122 .auth
1123 .get_user_relationship(&other.id, &profile.id)
1124 .await
1125 .0
1126 } else {
1127 RelationshipStatus::Unknown
1128 }
1129 };
1130
1131 let is_blocked = relationship == RelationshipStatus::Blocked;
1132
1133 if !is_helper && is_blocked {
1134 return Html(DatabaseError::NotFound.to_html(database));
1135 }
1136
1137 Html(
1138 FriendsTemplate {
1139 config: database.config.clone(),
1140 lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
1141 c.value_trimmed()
1142 } else {
1143 ""
1144 }),
1145 profile: auth_user.clone(),
1146 unread,
1147 notifs,
1148 other: other.clone(),
1149 friends: database
1150 .auth
1151 .get_user_participating_relationships_of_status_paginated(
1152 &other.id,
1153 RelationshipStatus::Friends,
1154 query.page,
1155 )
1156 .await
1157 .unwrap_or_default(),
1158 followers_count: database.auth.get_followers_count(&other.id).await,
1159 following_count: database.auth.get_following_count(&other.id).await,
1160 friends_count: database.auth.get_friendship_count_by_user(&other.id).await,
1161 page: query.page,
1162 is_self,
1164 is_helper,
1165 }
1166 .render()
1167 .unwrap(),
1168 )
1169}
1170
1171#[derive(Template)]
1172#[template(path = "profile/social/requests.html")]
1173struct FriendRequestsTemplate {
1174 config: Config,
1175 lang: langbeam::LangFile,
1176 profile: Option<Box<Profile>>,
1177 unread: usize,
1178 notifs: usize,
1179 other: Box<Profile>,
1180 requests: Vec<(Box<Profile>, Box<Profile>)>,
1181 followers_count: usize,
1182 following_count: usize,
1183 friends_count: usize,
1184 page: i32,
1185 is_self: bool,
1187 is_helper: bool,
1188}
1189
1190pub async fn friend_requests_request(
1192 jar: CookieJar,
1193 Path(username): Path<String>,
1194 State(database): State<Database>,
1195 Query(query): Query<PaginatedQuery>,
1196) -> impl IntoResponse {
1197 let auth_user = match jar.get("__Secure-Token") {
1198 Some(c) => match database
1199 .auth
1200 .get_profile_by_unhashed(c.value_trimmed())
1201 .await
1202 {
1203 Ok(ua) => ua,
1204 Err(_) => return Html(DatabaseError::NotAllowed.to_html(database)),
1205 },
1206 None => return Html(DatabaseError::NotAllowed.to_html(database)),
1207 };
1208
1209 let unread = database.get_inbox_count_by_recipient(&auth_user.id).await;
1210
1211 let notifs = database
1212 .auth
1213 .get_notification_count_by_recipient(&auth_user.id)
1214 .await;
1215
1216 let is_helper = {
1217 let group = match database.auth.get_group_by_id(auth_user.group).await {
1218 Ok(g) => g,
1219 Err(_) => return Html(DatabaseError::Other.to_html(database)),
1220 };
1221
1222 group.permissions.check_helper()
1223 };
1224
1225 let other = match database.auth.get_profile_by_username(&username).await {
1226 Ok(ua) => ua,
1227 Err(e) => return Html(e.to_string()),
1228 };
1229
1230 let is_self = auth_user.id == other.id;
1231
1232 if !is_self && !is_helper {
1233 return Html(DatabaseError::NotAllowed.to_html(database));
1234 }
1235
1236 Html(
1237 FriendRequestsTemplate {
1238 config: database.config.clone(),
1239 lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
1240 c.value_trimmed()
1241 } else {
1242 ""
1243 }),
1244 profile: Some(auth_user),
1245 unread,
1246 notifs,
1247 other: other.clone(),
1248 requests: database
1249 .auth
1250 .get_user_participating_relationships_of_status_paginated(
1251 &other.id,
1252 RelationshipStatus::Pending,
1253 query.page,
1254 )
1255 .await
1256 .unwrap(),
1257 followers_count: database.auth.get_followers_count(&other.id).await,
1258 following_count: database.auth.get_following_count(&other.id).await,
1259 friends_count: database.auth.get_friendship_count_by_user(&other.id).await,
1260 page: query.page,
1261 is_self,
1263 is_helper,
1264 }
1265 .render()
1266 .unwrap(),
1267 )
1268}
1269
1270#[derive(Template)]
1271#[template(path = "profile/social/blocks.html")]
1272struct BlocksTemplate {
1273 config: Config,
1274 lang: langbeam::LangFile,
1275 profile: Option<Box<Profile>>,
1276 unread: usize,
1277 notifs: usize,
1278 other: Box<Profile>,
1279 blocks: Vec<(Box<Profile>, Box<Profile>)>,
1280 followers_count: usize,
1281 following_count: usize,
1282 friends_count: usize,
1283 page: i32,
1284 is_self: bool,
1286 is_helper: bool,
1287}
1288
1289pub async fn blocks_request(
1291 jar: CookieJar,
1292 Path(username): Path<String>,
1293 State(database): State<Database>,
1294 Query(query): Query<PaginatedQuery>,
1295) -> impl IntoResponse {
1296 let auth_user = match jar.get("__Secure-Token") {
1297 Some(c) => match database
1298 .auth
1299 .get_profile_by_unhashed(c.value_trimmed())
1300 .await
1301 {
1302 Ok(ua) => ua,
1303 Err(_) => return Html(DatabaseError::NotAllowed.to_html(database)),
1304 },
1305 None => return Html(DatabaseError::NotAllowed.to_html(database)),
1306 };
1307
1308 let unread = database.get_inbox_count_by_recipient(&auth_user.id).await;
1309
1310 let notifs = database
1311 .auth
1312 .get_notification_count_by_recipient(&auth_user.id)
1313 .await;
1314
1315 let is_helper = {
1316 let group = match database.auth.get_group_by_id(auth_user.group).await {
1317 Ok(g) => g,
1318 Err(_) => return Html(DatabaseError::Other.to_html(database)),
1319 };
1320
1321 group.permissions.check_helper()
1322 };
1323
1324 if !is_helper {
1325 return Html(DatabaseError::NotAllowed.to_html(database));
1326 }
1327
1328 let other = match database.auth.get_profile_by_username(&username).await {
1329 Ok(ua) => ua,
1330 Err(e) => return Html(e.to_string()),
1331 };
1332
1333 let is_self = auth_user.id == other.id;
1334
1335 if !is_self && !is_helper {
1336 return Html(DatabaseError::NotAllowed.to_html(database));
1337 }
1338
1339 Html(
1340 BlocksTemplate {
1341 config: database.config.clone(),
1342 lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
1343 c.value_trimmed()
1344 } else {
1345 ""
1346 }),
1347 profile: Some(auth_user),
1348 unread,
1349 notifs,
1350 other: other.clone(),
1351 blocks: database
1352 .auth
1353 .get_user_participating_relationships_of_status_paginated(
1354 &other.id,
1355 RelationshipStatus::Blocked,
1356 query.page,
1357 )
1358 .await
1359 .unwrap(),
1360 followers_count: database.auth.get_followers_count(&other.id).await,
1361 following_count: database.auth.get_following_count(&other.id).await,
1362 friends_count: database.auth.get_friendship_count_by_user(&other.id).await,
1363 page: query.page,
1364 is_self,
1366 is_helper,
1367 }
1368 .render()
1369 .unwrap(),
1370 )
1371}
1372
1373#[derive(Template)]
1374#[template(path = "profile/questions.html")]
1375struct ProfileQuestionsTemplate {
1376 config: Config,
1377 lang: langbeam::LangFile,
1378 profile: Option<Box<Profile>>,
1379 unread: usize,
1380 notifs: usize,
1381 other: Box<Profile>,
1382 questions: Vec<(Question, usize, usize)>,
1383 questions_count: usize,
1384 response_count: usize,
1385 followers_count: usize,
1386 following_count: usize,
1387 friends_count: usize,
1388 is_following: bool,
1389 is_following_you: bool,
1390 metadata: String,
1391 page: i32,
1392 query: String,
1393 relationship: RelationshipStatus,
1395 lock_profile: bool,
1396 disallow_anonymous: bool,
1397 require_account: bool,
1398 hide_social: bool,
1399 unlocked: bool,
1400 is_powerful: bool,
1401 is_helper: bool,
1402 is_self: bool,
1403}
1404
1405pub async fn questions_request(
1407 jar: CookieJar,
1408 Path(username): Path<String>,
1409 State(database): State<Database>,
1410 Query(query): Query<ProfileQuery>,
1411) -> impl IntoResponse {
1412 let auth_user = match jar.get("__Secure-Token") {
1413 Some(c) => match database
1414 .auth
1415 .get_profile_by_unhashed(c.value_trimmed())
1416 .await
1417 {
1418 Ok(ua) => Some(ua),
1419 Err(_) => None,
1420 },
1421 None => None,
1422 };
1423
1424 let unread = if let Some(ref ua) = auth_user {
1425 database.get_inbox_count_by_recipient(&ua.id).await
1426 } else {
1427 0
1428 };
1429
1430 let notifs = if let Some(ref ua) = auth_user {
1431 database
1432 .auth
1433 .get_notification_count_by_recipient(&ua.id)
1434 .await
1435 } else {
1436 0
1437 };
1438
1439 let other = match database.auth.get_profile_by_username(&username).await {
1440 Ok(ua) => ua,
1441 Err(e) => return Html(e.to_string()),
1442 };
1443
1444 if other.metadata.is_true("rainbeam:authenticated_only") & auth_user.is_none() {
1445 return Html(DatabaseError::NotAllowed.to_html(database));
1447 }
1448
1449 let is_following = if let Some(ref ua) = auth_user {
1450 match database.auth.get_follow(&ua.id, &other.id).await {
1451 Ok(_) => true,
1452 Err(_) => false,
1453 }
1454 } else {
1455 false
1456 };
1457
1458 let is_following_you = if let Some(ref ua) = auth_user {
1459 match database.auth.get_follow(&other.id, &ua.id).await {
1460 Ok(_) => true,
1461 Err(_) => false,
1462 }
1463 } else {
1464 false
1465 };
1466
1467 let questions = if let Some(ref search) = query.q {
1468 match database
1469 .get_global_questions_by_author_searched_paginated(
1470 other.id.to_owned(),
1471 search.clone(),
1472 query.page,
1473 )
1474 .await
1475 {
1476 Ok(responses) => responses,
1477 Err(e) => return Html(e.to_html(database)),
1478 }
1479 } else {
1480 match database
1481 .get_global_questions_by_author_paginated(other.id.to_owned(), query.page)
1482 .await
1483 {
1484 Ok(responses) => responses,
1485 Err(e) => return Html(e.to_html(database)),
1486 }
1487 };
1488
1489 let mut is_helper: bool = false;
1490 let is_powerful = if let Some(ref ua) = auth_user {
1491 let group = match database.auth.get_group_by_id(ua.group).await {
1492 Ok(g) => g,
1493 Err(_) => return Html(DatabaseError::Other.to_html(database)),
1494 };
1495
1496 is_helper = group.permissions.check_helper();
1497 group.permissions.check_manager()
1498 } else {
1499 false
1500 };
1501
1502 let is_self = if let Some(ref profile) = auth_user {
1503 profile.id == other.id
1504 } else {
1505 false
1506 };
1507
1508 let relationship = if is_self {
1509 RelationshipStatus::Friends
1512 } else {
1513 if let Some(ref profile) = auth_user {
1514 database
1515 .auth
1516 .get_user_relationship(&other.id, &profile.id)
1517 .await
1518 .0
1519 } else {
1520 RelationshipStatus::Unknown
1521 }
1522 };
1523
1524 let is_blocked = relationship == RelationshipStatus::Blocked;
1525
1526 if !is_helper && is_blocked {
1527 return Html(DatabaseError::NotFound.to_html(database));
1528 }
1529
1530 Html(
1531 ProfileQuestionsTemplate {
1532 config: database.config.clone(),
1533 lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
1534 c.value_trimmed()
1535 } else {
1536 ""
1537 }),
1538 profile: auth_user.clone(),
1539 unread,
1540 notifs,
1541 other: other.clone(),
1542 questions,
1543 questions_count: database
1544 .get_global_questions_count_by_author(&other.id)
1545 .await,
1546 response_count: database.get_response_count_by_author(&other.id).await,
1547 followers_count: database.auth.get_followers_count(&other.id).await,
1548 following_count: database.auth.get_following_count(&other.id).await,
1549 friends_count: database.auth.get_friendship_count_by_user(&other.id).await,
1550 is_following,
1551 is_following_you,
1552 metadata: clean_metadata(&other.metadata),
1553 page: query.page,
1554 query: query.q.unwrap_or(String::new()),
1555 relationship,
1557 lock_profile: other
1558 .metadata
1559 .kv
1560 .get("sparkler:lock_profile")
1561 .unwrap_or(&"false".to_string())
1562 == "true",
1563 disallow_anonymous: other
1564 .metadata
1565 .kv
1566 .get("sparkler:disallow_anonymous")
1567 .unwrap_or(&"false".to_string())
1568 == "true",
1569 require_account: other
1570 .metadata
1571 .kv
1572 .get("sparkler:require_account")
1573 .unwrap_or(&"false".to_string())
1574 == "true",
1575 hide_social: (other
1576 .metadata
1577 .kv
1578 .get("sparkler:private_social")
1579 .unwrap_or(&"false".to_string())
1580 == "true")
1581 && !is_self,
1582 unlocked: if other.metadata.exists("rainbeam:view_password") {
1583 (other.metadata.soft_get("rainbeam:view_password") == query.password)
1584 | is_self
1585 | is_helper
1586 } else {
1587 true
1588 },
1589 is_powerful,
1590 is_helper,
1591 is_self,
1592 }
1593 .render()
1594 .unwrap(),
1595 )
1596}
1597
1598#[derive(Template)]
1599#[template(path = "profile/mod.html")]
1600struct ModTemplate {
1601 config: Config,
1602 lang: langbeam::LangFile,
1603 profile: Option<Box<Profile>>,
1604 unread: usize,
1605 notifs: usize,
1606 other: Box<Profile>,
1607 warnings: Vec<Warning>,
1608 response_count: usize,
1609 questions_count: usize,
1610 followers_count: usize,
1611 following_count: usize,
1612 friends_count: usize,
1613 is_following: bool,
1614 is_following_you: bool,
1615 metadata: String,
1616 badges: String,
1617 tokens: String,
1618 tokens_src: Vec<String>,
1619 relationship: RelationshipStatus,
1621 lock_profile: bool,
1622 disallow_anonymous: bool,
1623 require_account: bool,
1624 hide_social: bool,
1625 unlocked: bool,
1626 is_powerful: bool,
1627 is_helper: bool,
1628 is_self: bool,
1629}
1630
1631pub async fn mod_request(
1633 jar: CookieJar,
1634 Path(username): Path<String>,
1635 State(database): State<Database>,
1636 Query(query): Query<PasswordQuery>,
1637) -> impl IntoResponse {
1638 let auth_user = match jar.get("__Secure-Token") {
1639 Some(c) => match database
1640 .auth
1641 .get_profile_by_unhashed(c.value_trimmed())
1642 .await
1643 {
1644 Ok(ua) => ua,
1645 Err(_) => return Html(DatabaseError::NotAllowed.to_html(database)),
1646 },
1647 None => return Html(DatabaseError::NotAllowed.to_html(database)),
1648 };
1649
1650 let unread = database.get_inbox_count_by_recipient(&auth_user.id).await;
1651
1652 let notifs = database
1653 .auth
1654 .get_notification_count_by_recipient(&auth_user.id)
1655 .await;
1656
1657 let mut other = match database.auth.get_profile_by_username(&username).await {
1658 Ok(ua) => ua,
1659 Err(_) => return Html(DatabaseError::NotFound.to_html(database)),
1660 };
1661
1662 let is_following = match database.auth.get_follow(&auth_user.id, &other.id).await {
1663 Ok(_) => true,
1664 Err(_) => false,
1665 };
1666
1667 let is_following_you = match database.auth.get_follow(&other.id, &auth_user.id).await {
1668 Ok(_) => true,
1669 Err(_) => false,
1670 };
1671
1672 let mut is_helper: bool = false;
1673
1674 let group = match database.auth.get_group_by_id(auth_user.group).await {
1675 Ok(g) => g,
1676 Err(_) => return Html(DatabaseError::Other.to_html(database)),
1677 };
1678
1679 let is_powerful = {
1680 if group.permissions.check_helper() {
1681 is_helper = true;
1682 }
1683
1684 group.permissions.check_manager()
1685 };
1686
1687 if !group.permissions.check(FinePermission::VIEW_PROFILE_MANAGE) {
1688 return Html(DatabaseError::NotAllowed.to_html(database));
1689 }
1690
1691 if other.group == -1 {
1692 other.group = -2;
1693 }
1694
1695 let warnings = match database
1696 .auth
1697 .get_warnings_by_recipient(&other.id, auth_user.clone())
1698 .await
1699 {
1700 Ok(r) => r,
1701 Err(_) => return Html(DatabaseError::Other.to_html(database)),
1702 };
1703
1704 let is_self = auth_user.id == other.id;
1705 let relationship = RelationshipStatus::Friends; Html(
1708 ModTemplate {
1709 config: database.config.clone(),
1710 lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
1711 c.value_trimmed()
1712 } else {
1713 ""
1714 }),
1715 profile: Some(auth_user.clone()),
1716 unread,
1717 notifs,
1718 other: other.clone(),
1719 warnings,
1720 response_count: database.get_response_count_by_author(&other.id).await,
1721 questions_count: database
1722 .get_global_questions_count_by_author(&other.id)
1723 .await,
1724 followers_count: database.auth.get_followers_count(&other.id).await,
1725 following_count: database.auth.get_following_count(&other.id).await,
1726 friends_count: database.auth.get_friendship_count_by_user(&other.id).await,
1727 is_following,
1728 is_following_you,
1729 metadata: clean_metadata(&other.metadata),
1730 badges: serde_json::to_string_pretty(&other.badges).unwrap(),
1731 tokens: serde_json::to_string(&other.tokens).unwrap(),
1732 tokens_src: other.tokens.clone(),
1733 relationship,
1735 lock_profile: other
1736 .metadata
1737 .kv
1738 .get("sparkler:lock_profile")
1739 .unwrap_or(&"false".to_string())
1740 == "true",
1741 disallow_anonymous: other
1742 .metadata
1743 .kv
1744 .get("sparkler:disallow_anonymous")
1745 .unwrap_or(&"false".to_string())
1746 == "true",
1747 require_account: other
1748 .metadata
1749 .kv
1750 .get("sparkler:require_account")
1751 .unwrap_or(&"false".to_string())
1752 == "true",
1753 hide_social: (other
1754 .metadata
1755 .kv
1756 .get("sparkler:private_social")
1757 .unwrap_or(&"false".to_string())
1758 == "true")
1759 && !is_self,
1760 unlocked: if other.metadata.exists("rainbeam:view_password") {
1761 (other.metadata.soft_get("rainbeam:view_password") == query.password)
1762 | is_self
1763 | is_helper
1764 } else {
1765 true
1766 },
1767 is_powerful,
1768 is_helper,
1769 is_self,
1770 }
1771 .render()
1772 .unwrap(),
1773 )
1774}
1775
1776#[derive(Template)]
1777#[template(path = "profile/inbox.html")]
1778struct ProfileQuestionsInboxTemplate {
1779 config: Config,
1780 lang: langbeam::LangFile,
1781 profile: Option<Box<Profile>>,
1782 unread: usize,
1783 notifs: usize,
1784 other: Box<Profile>,
1785 questions: Vec<Question>,
1786 questions_count: usize,
1787 response_count: usize,
1788 followers_count: usize,
1789 following_count: usize,
1790 friends_count: usize,
1791 is_following: bool,
1792 is_following_you: bool,
1793 metadata: String,
1794 relationship: RelationshipStatus,
1796 lock_profile: bool,
1797 disallow_anonymous: bool,
1798 require_account: bool,
1799 hide_social: bool,
1800 unlocked: bool,
1801 is_powerful: bool,
1802 is_helper: bool,
1803 is_self: bool,
1804}
1805
1806pub async fn inbox_request(
1808 jar: CookieJar,
1809 Path(username): Path<String>,
1810 State(database): State<Database>,
1811 Query(query): Query<PasswordQuery>,
1812) -> impl IntoResponse {
1813 let auth_user = match jar.get("__Secure-Token") {
1814 Some(c) => match database
1815 .auth
1816 .get_profile_by_unhashed(c.value_trimmed())
1817 .await
1818 {
1819 Ok(ua) => ua,
1820 Err(_) => return Html(DatabaseError::NotAllowed.to_html(database)),
1821 },
1822 None => return Html(DatabaseError::NotAllowed.to_html(database)),
1823 };
1824
1825 let unread = database.get_inbox_count_by_recipient(&auth_user.id).await;
1826
1827 let notifs = database
1828 .auth
1829 .get_notification_count_by_recipient(&auth_user.id)
1830 .await;
1831
1832 let other = match database.auth.get_profile_by_username(&username).await {
1833 Ok(ua) => ua,
1834 Err(e) => return Html(e.to_string()),
1835 };
1836
1837 let is_following = match database.auth.get_follow(&auth_user.id, &other.id).await {
1838 Ok(_) => true,
1839 Err(_) => false,
1840 };
1841
1842 let is_following_you = match database.auth.get_follow(&other.id, &auth_user.id).await {
1843 Ok(_) => true,
1844 Err(_) => false,
1845 };
1846
1847 let questions = match database.get_questions_by_recipient(&other.id).await {
1848 Ok(responses) => responses,
1849 Err(e) => return Html(e.to_html(database)),
1850 };
1851
1852 let mut is_helper: bool = false;
1853 let is_powerful = {
1854 let group = match database.auth.get_group_by_id(auth_user.group).await {
1855 Ok(g) => g,
1856 Err(_) => return Html(DatabaseError::Other.to_html(database)),
1857 };
1858
1859 if group.permissions.check_helper() {
1860 is_helper = true;
1861 }
1862
1863 group.permissions.check_manager()
1864 };
1865
1866 if !is_powerful {
1867 return Html(DatabaseError::NotAllowed.to_html(database));
1868 }
1869
1870 let is_self = auth_user.id == other.id;
1871
1872 let relationship = database
1873 .auth
1874 .get_user_relationship(&other.id, &auth_user.id)
1875 .await
1876 .0;
1877
1878 let is_blocked = relationship == RelationshipStatus::Blocked;
1879
1880 if !is_helper && is_blocked {
1881 return Html(DatabaseError::NotFound.to_html(database));
1882 }
1883
1884 Html(
1885 ProfileQuestionsInboxTemplate {
1886 config: database.config.clone(),
1887 lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
1888 c.value_trimmed()
1889 } else {
1890 ""
1891 }),
1892 profile: Some(auth_user.clone()),
1893 unread,
1894 notifs,
1895 other: other.clone(),
1896 questions,
1897 questions_count: database
1898 .get_global_questions_count_by_author(&other.id)
1899 .await,
1900 response_count: database.get_response_count_by_author(&other.id).await,
1901 followers_count: database.auth.get_followers_count(&other.id).await,
1902 following_count: database.auth.get_following_count(&other.id).await,
1903 friends_count: database.auth.get_friendship_count_by_user(&other.id).await,
1904 is_following,
1905 is_following_you,
1906 metadata: clean_metadata(&other.metadata),
1907 relationship,
1909 lock_profile: other
1910 .metadata
1911 .kv
1912 .get("sparkler:lock_profile")
1913 .unwrap_or(&"false".to_string())
1914 == "true",
1915 disallow_anonymous: other
1916 .metadata
1917 .kv
1918 .get("sparkler:disallow_anonymous")
1919 .unwrap_or(&"false".to_string())
1920 == "true",
1921 require_account: other
1922 .metadata
1923 .kv
1924 .get("sparkler:require_account")
1925 .unwrap_or(&"false".to_string())
1926 == "true",
1927 hide_social: (other
1928 .metadata
1929 .kv
1930 .get("sparkler:private_social")
1931 .unwrap_or(&"false".to_string())
1932 == "true")
1933 && !is_self,
1934 unlocked: if other.metadata.exists("rainbeam:view_password") {
1935 (other.metadata.soft_get("rainbeam:view_password") == query.password)
1936 | is_self
1937 | is_helper
1938 } else {
1939 true
1940 },
1941 is_powerful,
1942 is_helper,
1943 is_self,
1944 }
1945 .render()
1946 .unwrap(),
1947 )
1948}
1949
1950#[derive(Template)]
1951#[template(path = "profile/outbox.html")]
1952struct ProfileQuestionsOutboxTemplate {
1953 config: Config,
1954 lang: langbeam::LangFile,
1955 profile: Option<Box<Profile>>,
1956 unread: usize,
1957 notifs: usize,
1958 other: Box<Profile>,
1959 questions: Vec<Question>,
1960 questions_count: usize,
1961 response_count: usize,
1962 followers_count: usize,
1963 following_count: usize,
1964 friends_count: usize,
1965 is_following: bool,
1966 is_following_you: bool,
1967 metadata: String,
1968 page: i32,
1969 relationship: RelationshipStatus,
1971 lock_profile: bool,
1972 disallow_anonymous: bool,
1973 require_account: bool,
1974 hide_social: bool,
1975 unlocked: bool,
1976 is_powerful: bool,
1977 is_helper: bool,
1978 is_self: bool,
1979}
1980
1981pub async fn outbox_request(
1983 jar: CookieJar,
1984 Path(username): Path<String>,
1985 State(database): State<Database>,
1986 Query(query): Query<ProfileQuery>,
1987) -> impl IntoResponse {
1988 let auth_user = match jar.get("__Secure-Token") {
1989 Some(c) => match database
1990 .auth
1991 .get_profile_by_unhashed(c.value_trimmed())
1992 .await
1993 {
1994 Ok(ua) => ua,
1995 Err(_) => return Html(DatabaseError::NotAllowed.to_html(database)),
1996 },
1997 None => return Html(DatabaseError::NotAllowed.to_html(database)),
1998 };
1999
2000 let unread = database.get_inbox_count_by_recipient(&auth_user.id).await;
2001
2002 let notifs = database
2003 .auth
2004 .get_notification_count_by_recipient(&auth_user.id)
2005 .await;
2006
2007 let other = match database.auth.get_profile_by_username(&username).await {
2008 Ok(ua) => ua,
2009 Err(e) => return Html(e.to_string()),
2010 };
2011
2012 let is_following = match database.auth.get_follow(&auth_user.id, &other.id).await {
2013 Ok(_) => true,
2014 Err(_) => false,
2015 };
2016
2017 let is_following_you = match database.auth.get_follow(&other.id, &auth_user.id).await {
2018 Ok(_) => true,
2019 Err(_) => false,
2020 };
2021
2022 let questions = match database
2023 .get_questions_by_author_paginated(other.id.to_owned(), query.page)
2024 .await
2025 {
2026 Ok(responses) => responses,
2027 Err(e) => return Html(e.to_html(database)),
2028 };
2029
2030 let mut is_helper: bool = false;
2031 let is_powerful = {
2032 let group = match database.auth.get_group_by_id(auth_user.group).await {
2033 Ok(g) => g,
2034 Err(_) => return Html(DatabaseError::Other.to_html(database)),
2035 };
2036
2037 if group.permissions.check_helper() {
2038 is_helper = true;
2039 }
2040
2041 group.permissions.check_manager()
2042 };
2043
2044 let is_self = auth_user.id == other.id;
2045
2046 if !is_powerful && !is_self {
2047 return Html(DatabaseError::NotAllowed.to_html(database));
2048 }
2049
2050 let relationship = database
2051 .auth
2052 .get_user_relationship(&other.id, &auth_user.id)
2053 .await
2054 .0;
2055
2056 let is_blocked = relationship == RelationshipStatus::Blocked;
2057
2058 if !is_helper && is_blocked {
2059 return Html(DatabaseError::NotFound.to_html(database));
2060 }
2061
2062 Html(
2063 ProfileQuestionsOutboxTemplate {
2064 config: database.config.clone(),
2065 lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
2066 c.value_trimmed()
2067 } else {
2068 ""
2069 }),
2070 profile: Some(auth_user.clone()),
2071 unread,
2072 notifs,
2073 other: other.clone(),
2074 questions,
2075 questions_count: database
2076 .get_global_questions_count_by_author(&other.id)
2077 .await,
2078 response_count: database.get_response_count_by_author(&other.id).await,
2079 followers_count: database.auth.get_followers_count(&other.id).await,
2080 following_count: database.auth.get_following_count(&other.id).await,
2081 friends_count: database.auth.get_friendship_count_by_user(&other.id).await,
2082 is_following,
2083 is_following_you,
2084 metadata: clean_metadata(&other.metadata),
2085 page: query.page,
2086 relationship,
2088 lock_profile: other
2089 .metadata
2090 .kv
2091 .get("sparkler:lock_profile")
2092 .unwrap_or(&"false".to_string())
2093 == "true",
2094 disallow_anonymous: other
2095 .metadata
2096 .kv
2097 .get("sparkler:disallow_anonymous")
2098 .unwrap_or(&"false".to_string())
2099 == "true",
2100 require_account: other
2101 .metadata
2102 .kv
2103 .get("sparkler:require_account")
2104 .unwrap_or(&"false".to_string())
2105 == "true",
2106 hide_social: (other
2107 .metadata
2108 .kv
2109 .get("sparkler:private_social")
2110 .unwrap_or(&"false".to_string())
2111 == "true")
2112 && !is_self,
2113 unlocked: if other.metadata.exists("rainbeam:view_password") {
2114 (other.metadata.soft_get("rainbeam:view_password") == query.password)
2115 | is_self
2116 | is_helper
2117 } else {
2118 true
2119 },
2120 is_powerful,
2121 is_helper,
2122 is_self,
2123 }
2124 .render()
2125 .unwrap(),
2126 )
2127}
2128
2129#[derive(Template)]
2130#[template(path = "profile/social/friend_request.html")]
2131struct FriendRequestTemplate {
2132 config: Config,
2133 lang: langbeam::LangFile,
2134 profile: Option<Box<Profile>>,
2135 unread: usize,
2136 notifs: usize,
2137 other: Box<Profile>,
2138}
2139
2140pub async fn friend_request(
2142 jar: CookieJar,
2143 Path(username): Path<String>,
2144 State(database): State<Database>,
2145) -> impl IntoResponse {
2146 let auth_user = match jar.get("__Secure-Token") {
2147 Some(c) => match database
2148 .auth
2149 .get_profile_by_unhashed(c.value_trimmed())
2150 .await
2151 {
2152 Ok(ua) => ua,
2153 Err(_) => return Html(DatabaseError::NotAllowed.to_html(database)),
2154 },
2155 None => return Html(DatabaseError::NotAllowed.to_html(database)),
2156 };
2157
2158 let unread = database.get_inbox_count_by_recipient(&auth_user.id).await;
2159
2160 let notifs = database
2161 .auth
2162 .get_notification_count_by_recipient(&auth_user.id)
2163 .await;
2164
2165 let other = match database.auth.get_profile_by_username(&username).await {
2166 Ok(ua) => ua,
2167 Err(e) => return Html(e.to_string()),
2168 };
2169
2170 let relationship = database
2171 .auth
2172 .get_user_relationship(&other.id, &auth_user.id)
2173 .await;
2174
2175 if (relationship.0 != RelationshipStatus::Pending) | (relationship.2 != auth_user.id) {
2177 return Html(DatabaseError::NotFound.to_html(database));
2178 }
2179
2180 Html(
2181 FriendRequestTemplate {
2182 config: database.config.clone(),
2183 lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
2184 c.value_trimmed()
2185 } else {
2186 ""
2187 }),
2188 profile: Some(auth_user.clone()),
2189 unread,
2190 notifs,
2191 other: other.clone(),
2192 }
2193 .render()
2194 .unwrap(),
2195 )
2196}
2197
2198#[derive(Template)]
2199#[template(path = "fun/styled_profile_card.html")]
2200struct CardTemplate {
2201 lang: langbeam::LangFile,
2202 profile: Option<Box<Profile>>,
2203 user: Box<Profile>,
2204}
2205
2206pub async fn render_card_request(
2208 jar: CookieJar,
2209 Path(username): Path<String>,
2210 State(database): State<Database>,
2211) -> impl IntoResponse {
2212 let auth_user = match jar.get("__Secure-Token") {
2213 Some(c) => match database
2214 .auth
2215 .get_profile_by_unhashed(c.value_trimmed())
2216 .await
2217 {
2218 Ok(ua) => Some(ua),
2219 Err(_) => None,
2220 },
2221 None => None,
2222 };
2223
2224 Html(
2225 CardTemplate {
2226 lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
2227 c.value_trimmed()
2228 } else {
2229 ""
2230 }),
2231 profile: auth_user,
2232 user: match database.get_profile(username).await {
2233 Ok(ua) => ua,
2234 Err(e) => return Html(e.to_html(database)),
2235 },
2236 }
2237 .render()
2238 .unwrap(),
2239 )
2240}
2241
2242#[derive(Template)]
2243#[template(path = "profile/warning.html")]
2244struct WarningTemplate {
2245 config: Config,
2246 lang: langbeam::LangFile,
2247 profile: Option<Box<Profile>>,
2248 other: Box<Profile>,
2249}
2250
2251pub async fn warning_request(
2253 jar: CookieJar,
2254 Path(username): Path<String>,
2255 State(database): State<Database>,
2256) -> impl IntoResponse {
2257 let auth_user = match jar.get("__Secure-Token") {
2258 Some(c) => match database
2259 .auth
2260 .get_profile_by_unhashed(c.value_trimmed())
2261 .await
2262 {
2263 Ok(ua) => Some(ua),
2264 Err(_) => None,
2265 },
2266 None => None,
2267 };
2268
2269 let other = match database.auth.get_profile_by_username(&username).await {
2270 Ok(ua) => ua,
2271 Err(e) => return Html(e.to_string()),
2272 };
2273
2274 Html(
2275 WarningTemplate {
2276 config: database.config.clone(),
2277 lang: database.lang(if let Some(c) = jar.get("net.rainbeam.langs.choice") {
2278 c.value_trimmed()
2279 } else {
2280 ""
2281 }),
2282 profile: auth_user.clone(),
2283 other: other.clone(),
2284 }
2285 .render()
2286 .unwrap(),
2287 )
2288}