1use std::collections::BTreeMap;
2
3use crate::layout::LayoutComponent;
4use crate::model::{
5 DatabaseError, FinePermission, IpBan, IpBanCreate, IpBlock, IpBlockCreate, Item, ItemCreate,
6 ItemEdit, ItemEditContent, ItemStatus, ItemType, Profile, ProfileCreate, ProfileMetadata,
7 RelationshipStatus, TokenContext, Transaction, TransactionCreate, UserLabel, Warning,
8 WarningCreate,
9};
10use crate::model::{Group, Notification, NotificationCreate, UserFollow};
11use hcaptcha_no_wasm::Hcaptcha;
12use rainbeam_shared::snow::AlmostSnowflake;
13use reqwest::Client as HttpClient;
14use serde::{Deserialize, Serialize};
15
16use databeam::{query as sqlquery, utility, prelude::*};
17use pathbufd::{PathBufD, pathd};
18
19pub use rainbeam_shared::config::HCaptchaConfig;
20
21pub type Result<T> = std::result::Result<T, DatabaseError>;
22use std::sync::LazyLock;
23
24use crate::{cache_sync, from_row, update_profile_count, simplify};
25
26pub static ALLOWED_CUSTOM_KEYS: LazyLock<Vec<&'static str>> = LazyLock::new(|| {
28 vec![
29 "sparkler:display_name",
30 "sparkler:status_note",
31 "sparkler:status_emoji",
32 "sparkler:limited_friend_requests",
33 "sparkler:limited_chats",
34 "sparkler:private_profile",
35 "sparkler:allow_drawings",
36 "sparkler:biography",
37 "sparkler:sidebar",
38 "sparkler:avatar_url",
39 "sparkler:banner_url",
40 "sparkler:banner_fit",
41 "sparkler:website_theme",
42 "sparkler:allow_profile_themes",
43 "sparkler:motivational_header",
44 "sparkler:warning",
45 "sparkler:anonymous_username",
46 "sparkler:anonymous_avatar",
47 "sparkler:pinned",
48 "sparkler:profile_theme",
49 "sparkler:desktop_tl_logo",
50 "sparkler:custom_css",
51 "sparkler:color_surface",
52 "sparkler:color_lowered",
53 "sparkler:color_super_lowered",
54 "sparkler:color_raised",
55 "sparkler:color_super_raised",
56 "sparkler:color_text",
57 "sparkler:color_text_raised",
58 "sparkler:color_text_lowered",
59 "sparkler:color_link",
60 "sparkler:color_primary",
61 "sparkler:color_primary_lowered",
62 "sparkler:color_primary_raised",
63 "sparkler:color_text_primary",
64 "sparkler:color_shadow",
65 "sparkler:lock_profile",
66 "sparkler:disallow_anonymous",
67 "sparkler:disallow_anonymous_comments",
68 "sparkler:require_account",
69 "sparkler:private_social",
70 "sparkler:filter",
71 "rainbeam:verify_url",
72 "rainbeam:verify_code",
73 "rainbeam:market_theme_template",
74 "rainbeam:nsfw_profile",
75 "rainbeam:share_hashtag",
76 "rainbeam:authenticated_only",
77 "rainbeam:force_default_layout",
78 "rainbeam:disallow_response_comments",
79 "rainbeam:view_password",
80 "rainbeam:do_not_send_global_questions_to_inbox",
81 "rainbeam:do_not_clear_inbox_count_on_view",
82 "rainbeam:do_not_send_global_questions_to_friends",
83 ]
84});
85
86#[derive(Serialize, Deserialize, Clone, Debug)]
87pub struct ServerOptions {
88 #[serde(default)]
90 pub registration_enabled: bool,
91 #[serde(default)]
93 pub captcha: HCaptchaConfig,
94 #[serde(default)]
96 pub real_ip_header: Option<String>,
97 #[serde(default)]
99 pub static_dir: PathBufD,
100 #[serde(default)]
102 pub media_dir: PathBufD,
103 #[serde(default)]
107 pub host: String,
108 pub snowflake_server_id: usize,
110 #[serde(default)]
112 pub blocked_hosts: Vec<String>,
113}
114
115impl Default for ServerOptions {
116 fn default() -> Self {
117 Self {
118 registration_enabled: true,
119 captcha: HCaptchaConfig::default(),
120 real_ip_header: Option::None,
121 static_dir: PathBufD::default(),
122 media_dir: PathBufD::default(),
123 host: String::new(),
124 snowflake_server_id: 1234567890,
125 blocked_hosts: Vec::new(),
126 }
127 }
128}
129
130#[derive(Clone)]
132pub struct Database {
133 pub base: StarterDatabase,
134 pub config: ServerOptions,
135 pub http: HttpClient,
136}
137
138impl Database {
139 pub async fn new(
141 database_options: databeam::DatabaseOpts,
142 server_options: ServerOptions,
143 ) -> Self {
144 let base = StarterDatabase::new(database_options).await;
145
146 Self {
147 base: base.clone(),
148 http: HttpClient::new(),
149 config: server_options,
150 }
151 }
152
153 pub fn env_options() -> databeam::DatabaseOpts {
155 use std::env::var;
156 databeam::DatabaseOpts {
157 r#type: match var("DB_TYPE") {
158 Ok(v) => Option::Some(v),
159 Err(_) => Option::None,
160 },
161 host: match var("DB_HOST") {
162 Ok(v) => Option::Some(v),
163 Err(_) => Option::None,
164 },
165 user: var("DB_USER").unwrap_or(String::new()),
166 pass: var("DB_PASS").unwrap_or(String::new()),
167 name: var("DB_NAME").unwrap_or(String::new()),
168 }
169 }
170
171 pub async fn init(&self) {
173 let c = &self.base.db.client;
175
176 let _ = sqlquery(
177 "CREATE TABLE IF NOT EXISTS \"xprofiles\" (
178 id TEXT,
179 username TEXT,
180 password TEXT,
181 tokens TEXT,
182 metadata TEXT,
183 joined TEXT,
184 gid TEXT,
185 salt TEXT,
186 ips TEXT,
187 badges TEXT,
188 tier TEXT,
189 token_context TEXT,
190 coins TEXT DEFAULT '0',
191 links TEXT DEFAULT '{}',
192 layout TEXT DEFAULT '{\"json\":\"default.json\"}',
193 question_count TEXT DEFAULT '0',
194 response_count TEXT DEFAULT '0',
195 totp TEXT DEFAULT '',
196 recovery_codes TEXT DEFAULT '[]'
197 notification_count TEXT DEFAULT '0',
198 inbox_count TEXT DEFAULT '0'
199 labels TEXT DEFAULT '[]',
200 )",
201 )
202 .execute(c)
203 .await;
204
205 let _ = sqlquery(
206 "CREATE TABLE IF NOT EXISTS \"xgroups\" (
207 name TEXT,
208 id TEXT,
209 permissions INTEGER
210 )",
211 )
212 .execute(c)
213 .await;
214
215 let _ = sqlquery(
216 "CREATE TABLE IF NOT EXISTS \"xfollows\" (
217 user TEXT,
218 following TEXT
219 )",
220 )
221 .execute(c)
222 .await;
223
224 let _ = sqlquery(
225 "CREATE TABLE IF NOT EXISTS \"xnotifications\" (
226 title TEXT,
227 content TEXT,
228 address TEXT,
229 timestamp TEXT,
230 id TEXT,
231 recipient TEXT
232 )",
233 )
234 .execute(c)
235 .await;
236
237 let _ = sqlquery(
238 "CREATE TABLE IF NOT EXISTS \"xwarnings\" (
239 id TEXT,
240 content TEXT,
241 timestamp TEXT,
242 recipient TEXT,
243 moderator TEXT
244 )",
245 )
246 .execute(c)
247 .await;
248
249 let _ = sqlquery(
250 "CREATE TABLE IF NOT EXISTS \"xbans\" (
251 id TEXT,
252 ip TEXT,
253 reason TEXT,
254 moderator TEXT,
255 timestamp TEXT
256 )",
257 )
258 .execute(c)
259 .await;
260
261 let _ = sqlquery(
262 "CREATE TABLE IF NOT EXISTS \"xrelationships\" (
263 one TEXT,
264 two TEXT,
265 status TEXT,
266 timestamp TEXT
267 )",
268 )
269 .execute(c)
270 .await;
271
272 let _ = sqlquery(
273 "CREATE TABLE IF NOT EXISTS \"xipblocks\" (
274 id TEXT,
275 ip TEXT,
276 user TEXT,
277 context TEXT,
278 timestamp TEXT
279 )",
280 )
281 .execute(c)
282 .await;
283
284 let _ = sqlquery(
285 "CREATE TABLE IF NOT EXISTS \"xlabels\" (
286 id TEXT,
287 name TEXT,
288 timestamp TEXT,
289 creator TEXT
290 )",
291 )
292 .execute(c)
293 .await;
294
295 let _ = sqlquery(
296 "CREATE TABLE IF NOT EXISTS \"xugc_transactions\" (
298 id TEXT,
299 amount TEXT,
300 item TEXT,
301 timestamp TEXT,
302 customer TEXT,
303 merchant TEXT
304 )",
305 )
306 .execute(c)
307 .await;
308
309 let _ = sqlquery(
310 "CREATE TABLE IF NOT EXISTS \"xugc_items\" (
311 id TEXT,
312 name TEXT,
313 description TEXT,
314 cost TEXT,
315 content TEXT,
316 type TEXT,
317 status TEXT,
318 timestamp TEXT,
319 creator TEXT
320 )",
321 )
322 .execute(c)
323 .await;
324 }
325
326 pub async fn audit(&self, actor_id: &str, content: &str) -> Result<()> {
330 match self
331 .create_notification(
332 NotificationCreate {
333 title: format!("[{actor_id}](/+u/{actor_id})"),
334 content: content.to_string(),
335 address: format!("/+u/{actor_id}"),
336 recipient: "*(audit)".to_string(), },
338 None,
339 )
340 .await
341 {
342 Ok(_) => Ok(()),
343 Err(_) => Err(DatabaseError::Other),
344 }
345 }
346
347 pub async fn gimme_profile(&self, row: BTreeMap<String, String>) -> Result<Box<Profile>> {
351 let id = from_row!(row->id());
352
353 let metadata: ProfileMetadata = from_row!(row->metadata(json); DatabaseError::ValueError);
354 let do_not_clear_inbox_count_on_view =
355 metadata.is_true("rainbeam:do_not_clear_inbox_count_on_view");
356
357 Ok(Box::new(Profile {
358 id: id.clone(),
359 username: from_row!(row->username()),
360 password: from_row!(row->password()),
361 salt: from_row!(row->salt(); &String::new()),
362 tokens: from_row!(row->tokens(json); DatabaseError::ValueError),
363 ips: from_row!(row->ips(json); DatabaseError::ValueError),
364 token_context: from_row!(row->token_context(json); DatabaseError::ValueError),
365 metadata,
366 badges: from_row!(row->badges(json); DatabaseError::ValueError),
367 group: from_row!(row->gid(i32); 0),
368 joined: from_row!(row->joined(u128); 0),
369 tier: from_row!(row->tier(i32); 0),
370 labels: from_row!(row->labels(json); DatabaseError::ValueError),
371 coins: from_row!(row->coins(i32); 0),
372 links: from_row!(row->links(json); DatabaseError::ValueError),
373 layout: from_row!(row->layout(json); DatabaseError::ValueError),
374 question_count: from_row!(row->question_count(usize); 0),
375 response_count: cache_sync!(
376 |row, id| response_count->(update_profile_response_count in self) {1}
377 ),
378 totp: from_row!(row->totp()),
379 recovery_codes: from_row!(row->recovery_codes(json); DatabaseError::ValueError),
380 notification_count: from_row!(row->notification_count(usize); 0),
381 inbox_count: if do_not_clear_inbox_count_on_view {
382 cache_sync!(|row, id| inbox_count->(update_profile_inbox_count in self) {1})
384 } else {
385 from_row!(row->inbox_count(usize); 0)
386 },
387 }))
388 }
389
390 fn is_digit(&self, input: &str) -> bool {
392 for char in input.chars() {
393 if !char.is_numeric() {
394 return false;
395 }
396 }
397
398 true
399 }
400
401 pub async fn get_profile_username(&self, id: &str) -> String {
403 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
405 "SELECT \"username\" FROM \"xprofiles\" WHERE \"id\" = ?"
406 } else {
407 "SELECT \"username\" FROM \"xprofiles\" WHERE \"id\" = $1"
408 };
409
410 let c = &self.base.db.client;
411 let row = match sqlquery(query).bind::<&str>(id).fetch_one(c).await {
412 Ok(u) => self.base.textify_row(u).0,
413 Err(_) => return String::new(),
414 };
415
416 from_row!(row->username())
418 }
419
420 pub async fn get_profile(&self, id: &str) -> Result<Box<Profile>> {
422 let mut id = id.to_string();
423 if id.starts_with("ANSWERED:") {
424 id = id.replace("ANSWERED:", "");
426 }
427
428 if id == "@" {
429 return Ok(Box::new(Profile::global()));
430 } else if id.starts_with("anonymous#") | (id == "anonymous") | (id == "#") {
431 let tag = Profile::anonymous_tag(&id);
432 return Ok(Box::new(Profile::anonymous(tag.3)));
433 } else if (id == "0") | (id == "system") {
434 return Ok(Box::new(Profile::system()));
435 }
436
437 if id.contains("%") {
439 id = id
440 .split("%")
441 .collect::<Vec<&str>>()
442 .get(0)
443 .unwrap()
444 .to_string();
445 }
446
447 if (id.len() <= 32) && (!self.is_digit(&id) | (id.len() < 18)) {
449 return match self.get_profile_by_username(&id).await {
450 Ok(ua) => Ok(ua),
451 Err(e) => return Err(e),
452 };
453 }
454
455 match self.get_profile_by_id(&id).await {
456 Ok(ua) => Ok(ua),
457 Err(e) => return Err(e),
458 }
459 }
460
461 pub async fn get_profile_by_hashed(&self, hashed: &str) -> Result<Box<Profile>> {
466 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
468 "SELECT * FROM \"xprofiles\" WHERE \"tokens\" LIKE ?"
469 } else {
470 "SELECT * FROM \"xprofiles\" WHERE \"tokens\" LIKE $1"
471 };
472
473 let c = &self.base.db.client;
474 let row = match sqlquery(query)
475 .bind::<&str>(&format!("%\"{hashed}\"%"))
476 .fetch_one(c)
477 .await
478 {
479 Ok(u) => self.base.textify_row(u).0,
480 Err(_) => return Err(DatabaseError::Other),
481 };
482
483 Ok(match self.gimme_profile(row).await {
485 Ok(ua) => ua,
486 Err(e) => return Err(e),
487 })
488 }
489
490 pub async fn get_profile_by_unhashed(&self, unhashed: &str) -> Result<Box<Profile>> {
495 self.get_profile_by_hashed(&utility::hash(unhashed.to_string()))
496 .await
497 }
498
499 pub async fn get_profile_by_ip(&self, ip: &str) -> Result<Box<Profile>> {
504 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
506 "SELECT * FROM \"xprofiles\" WHERE \"ips\" LIKE ?"
507 } else {
508 "SELECT * FROM \"xprofiles\" WHERE \"ips\" LIKE $1"
509 };
510
511 let c = &self.base.db.client;
512 let row = match sqlquery(query)
513 .bind::<&str>(&format!("%\"{ip}\"%"))
514 .fetch_one(c)
515 .await
516 {
517 Ok(u) => self.base.textify_row(u).0,
518 Err(_) => return Err(DatabaseError::Other),
519 };
520
521 Ok(match self.gimme_profile(row).await {
523 Ok(ua) => ua,
524 Err(e) => return Err(e),
525 })
526 }
527
528 pub async fn get_profile_by_username(&self, username: &str) -> Result<Box<Profile>> {
533 let username = username.to_lowercase();
534
535 let cached = self
537 .base
538 .cache
539 .get(format!("rbeam.auth.profile:{}", username))
540 .await;
541
542 if cached.is_some() {
543 match serde_json::from_str::<Profile>(cached.unwrap().as_str()) {
544 Ok(p) => return Ok(Box::new(p)),
545 Err(_) => {
546 self.base
547 .cache
548 .remove(format!("rbeam.auth.profile:{}", username))
549 .await;
550 }
551 };
552 }
553
554 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
556 "SELECT * FROM \"xprofiles\" WHERE \"username\" = ?"
557 } else {
558 "SELECT * FROM \"xprofiles\" WHERE \"username\" = $1"
559 };
560
561 let c = &self.base.db.client;
562 let row = match sqlquery(query).bind::<&str>(&username).fetch_one(c).await {
563 Ok(r) => self.base.textify_row(r).0,
564 Err(_) => return Err(DatabaseError::NotFound),
565 };
566
567 let user = match self.gimme_profile(row).await {
569 Ok(ua) => ua,
570 Err(e) => return Err(e),
571 };
572
573 self.base
574 .cache
575 .set(
576 format!("rbeam.auth.profile:{}", username),
577 serde_json::to_string::<Profile>(&user).unwrap(),
578 )
579 .await;
580
581 Ok(user)
583 }
584
585 pub async fn get_profile_by_id(&self, id: &str) -> Result<Box<Profile>> {
590 let id = id.to_lowercase();
591
592 let cached = self
594 .base
595 .cache
596 .get(format!("rbeam.auth.profile:{}", id))
597 .await;
598
599 if cached.is_some() {
600 match serde_json::from_str::<Profile>(cached.unwrap().as_str()) {
601 Ok(p) => return Ok(Box::new(p)),
602 Err(_) => {
603 self.base
604 .cache
605 .remove(format!("rbeam.auth.profile:{}", id))
606 .await;
607 }
608 };
609 }
610
611 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
613 "SELECT * FROM \"xprofiles\" WHERE \"id\" = ?"
614 } else {
615 "SELECT * FROM \"xprofiles\" WHERE \"id\" = $1"
616 };
617
618 let c = &self.base.db.client;
619 let row = match sqlquery(query).bind::<&String>(&id).fetch_one(c).await {
620 Ok(r) => self.base.textify_row(r).0,
621 Err(_) => return Err(DatabaseError::NotFound),
622 };
623
624 let user = match self.gimme_profile(row).await {
626 Ok(ua) => ua,
627 Err(e) => return Err(e),
628 };
629
630 self.base
631 .cache
632 .set(
633 format!("rbeam.auth.profile:{}", id),
634 serde_json::to_string::<Profile>(&user).unwrap(),
635 )
636 .await;
637
638 Ok(user)
640 }
641
642 pub fn validate_username(username: &str) -> Result<()> {
644 let banned_usernames = &[
645 "admin",
646 "account",
647 "anonymous",
648 "login",
649 "sign_up",
650 "settings",
651 "api",
652 "intents",
653 "circles",
654 "chats",
655 "sites",
656 "responses",
657 "questions",
658 "comments",
659 "response",
660 "question",
661 "comment",
662 "pages",
663 "inbox",
664 "system",
665 "market",
666 ".well-known",
667 "static",
668 ];
669
670 let regex = regex::RegexBuilder::new(r"[^\w_\-\.!]+")
671 .multi_line(true)
672 .build()
673 .unwrap();
674
675 if regex.captures(&username).is_some() {
676 return Err(DatabaseError::ValueError);
677 }
678
679 if (username.len() < 2) | (username.len() > 500) {
680 return Err(DatabaseError::ValueError);
681 }
682
683 if banned_usernames.contains(&username) {
684 return Err(DatabaseError::ValueError);
685 }
686
687 Ok(())
688 }
689
690 pub async fn create_profile(&self, props: ProfileCreate, user_ip: &str) -> Result<String> {
697 if self.config.registration_enabled == false {
698 return Err(DatabaseError::NotAllowed);
699 }
700
701 let username = props.username.trim();
703 let password = props.password.trim();
704
705 if let Err(_) = props
707 .valid_response(&self.config.captcha.secret, None)
708 .await
709 {
710 return Err(DatabaseError::NotAllowed);
711 }
712
713 if let Ok(_) = &self.get_profile_by_username(username).await {
715 return Err(DatabaseError::UsernameTaken);
716 };
717
718 if let Err(e) = Database::validate_username(username) {
720 return Err(e);
721 }
722
723 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
725 "INSERT INTO \"xprofiles\" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
726 } else {
727 "INSERT INTO \"xprofiles\" VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22)"
728 };
729
730 let user_token_unhashed: &str = &databeam::utility::uuid();
731 let user_token_hashed: &str = &databeam::utility::hash(user_token_unhashed.to_string());
732 let salt: &str = &rainbeam_shared::hash::salt();
733
734 let timestamp = utility::unix_epoch_timestamp();
735
736 let c = &self.base.db.client;
737 match sqlquery(query)
738 .bind::<&str>(&AlmostSnowflake::new(self.config.snowflake_server_id).to_string())
740 .bind::<&str>(&username.to_lowercase())
741 .bind::<&str>(&rainbeam_shared::hash::hash_salted(
742 password.to_string(),
743 salt.to_string(),
744 ))
745 .bind::<&str>(&serde_json::to_string::<Vec<&str>>(&vec![user_token_hashed]).unwrap())
746 .bind::<&str>(
747 &serde_json::to_string::<ProfileMetadata>(&ProfileMetadata::default()).unwrap(),
748 )
749 .bind::<&String>(×tamp.to_string())
750 .bind::<i8>(0)
751 .bind::<&str>(&salt)
752 .bind::<&str>(&serde_json::to_string::<Vec<&str>>(&vec![user_ip]).unwrap())
753 .bind::<&str>("[]")
754 .bind::<i8>(0)
755 .bind::<&str>("[]")
756 .bind::<i8>(0)
757 .bind::<&str>("{}")
758 .bind::<&str>("{\"json\":\"default.json\"}")
759 .bind::<i8>(0)
760 .bind::<i8>(0)
761 .bind::<&str>("")
762 .bind::<&str>("[]")
763 .bind::<i8>(0)
764 .bind::<i8>(0)
765 .bind::<&str>("[]")
766 .execute(c)
767 .await
768 {
769 Ok(_) => Ok(user_token_unhashed.to_string()),
770 Err(_) => Err(DatabaseError::Other),
771 }
772 }
773
774 update_profile_count!(update_profile_question_count, question_count);
775 update_profile_count!(update_profile_response_count, response_count);
776 update_profile_count!(update_profile_inbox_count, inbox_count);
777 update_profile_count!(update_profile_notification_count, notification_count);
778
779 pub async fn update_profile_metadata(
781 &self,
782 id: &str,
783 mut metadata: ProfileMetadata,
784 ) -> Result<()> {
785 let profile = match self.get_profile(&id).await {
787 Ok(ua) => ua,
788 Err(e) => return Err(e),
789 };
790
791 for kv in metadata.kv.clone() {
793 if !ALLOWED_CUSTOM_KEYS.contains(&kv.0.as_str()) {
794 metadata.kv.remove(&kv.0);
795 }
796 }
797
798 if !metadata.check() {
799 return Err(DatabaseError::TooLong);
800 }
801
802 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
804 "UPDATE \"xprofiles\" SET \"metadata\" = ? WHERE \"id\" = ?"
805 } else {
806 "UPDATE \"xprofiles\" SET (\"metadata\") = ($1) WHERE \"id\" = $2"
807 };
808
809 let c = &self.base.db.client;
810 let meta = &serde_json::to_string(&metadata).unwrap();
811 match sqlquery(query)
812 .bind::<&str>(meta)
813 .bind::<&str>(&id)
814 .execute(c)
815 .await
816 {
817 Ok(_) => {
818 self.base
819 .cache
820 .remove(format!("rbeam.auth.profile:{}", profile.username))
821 .await;
822
823 self.base
824 .cache
825 .remove(format!("rbeam.auth.profile:{}", profile.id))
826 .await;
827
828 Ok(())
829 }
830 Err(_) => Err(DatabaseError::Other),
831 }
832 }
833
834 pub async fn update_profile_tokens(
836 &self,
837 id: &str,
838 tokens: Vec<String>,
839 ips: Vec<String>,
840 token_context: Vec<TokenContext>,
841 ) -> Result<()> {
842 let ua = match self.get_profile(&id).await {
844 Ok(ua) => ua,
845 Err(e) => return Err(e),
846 };
847
848 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
850 "UPDATE \"xprofiles\" SET \"tokens\" = ?, \"ips\" = ?, \"token_context\" = ? WHERE \"id\" = ?"
851 } else {
852 "UPDATE \"xprofiles\" SET (\"tokens\", \"ips\") = ($1, $2, $3) WHERE \"id\" = $4"
853 };
854
855 let c = &self.base.db.client;
856
857 let tokens = &serde_json::to_string(&tokens).unwrap();
858 let ips = &serde_json::to_string(&ips).unwrap();
859 let token_context = &serde_json::to_string(&token_context).unwrap();
860
861 match sqlquery(query)
862 .bind::<&str>(tokens)
863 .bind::<&str>(ips)
864 .bind::<&str>(token_context)
865 .bind::<&str>(&ua.id)
866 .execute(c)
867 .await
868 {
869 Ok(_) => {
870 self.base
871 .cache
872 .remove(format!("rbeam.auth.profile:{}", ua.username))
873 .await;
874
875 self.base
876 .cache
877 .remove(format!("rbeam.auth.profile:{}", ua.id))
878 .await;
879
880 Ok(())
881 }
882 Err(_) => Err(DatabaseError::Other),
883 }
884 }
885
886 pub async fn update_profile_badges(
888 &self,
889 id: &str,
890 badges: Vec<(String, String, String)>,
891 ) -> Result<()> {
892 let ua = match self.get_profile(&id).await {
894 Ok(ua) => ua,
895 Err(e) => return Err(e),
896 };
897
898 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
900 "UPDATE \"xprofiles\" SET \"badges\" = ? WHERE \"id\" = ?"
901 } else {
902 "UPDATE \"xprofiles\" SET (\"badges\") = ($1) WHERE \"id\" = $2"
903 };
904
905 let c = &self.base.db.client;
906 let badges = &serde_json::to_string(&badges).unwrap();
907
908 match sqlquery(query)
909 .bind::<&str>(badges)
910 .bind::<&str>(&id)
911 .execute(c)
912 .await
913 {
914 Ok(_) => {
915 self.base
916 .cache
917 .remove(format!("rbeam.auth.profile:{}", ua.username))
918 .await;
919
920 self.base
921 .cache
922 .remove(format!("rbeam.auth.profile:{}", ua.id))
923 .await;
924
925 Ok(())
926 }
927 Err(_) => Err(DatabaseError::Other),
928 }
929 }
930
931 pub async fn update_profile_labels(&self, id: &str, labels: Vec<i64>) -> Result<()> {
933 let ua = match self.get_profile(&id).await {
935 Ok(ua) => ua,
936 Err(e) => return Err(e),
937 };
938
939 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
941 "UPDATE \"xprofiles\" SET \"labels\" = ? WHERE \"id\" = ?"
942 } else {
943 "UPDATE \"xprofiles\" SET (\"labels\") = ($1) WHERE \"id\" = $2"
944 };
945
946 let c = &self.base.db.client;
947 let labels = &serde_json::to_string(&labels).unwrap();
948
949 match sqlquery(query)
950 .bind::<&str>(labels)
951 .bind::<&str>(&id)
952 .execute(c)
953 .await
954 {
955 Ok(_) => {
956 self.base
957 .cache
958 .remove(format!("rbeam.auth.profile:{}", ua.username))
959 .await;
960
961 self.base
962 .cache
963 .remove(format!("rbeam.auth.profile:{}", ua.id))
964 .await;
965
966 Ok(())
967 }
968 Err(_) => Err(DatabaseError::Other),
969 }
970 }
971
972 pub async fn update_profile_links(
974 &self,
975 id: &str,
976 links: BTreeMap<String, String>,
977 ) -> Result<()> {
978 let ua = match self.get_profile(&id).await {
980 Ok(ua) => ua,
981 Err(e) => return Err(e),
982 };
983
984 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
986 "UPDATE \"xprofiles\" SET \"links\" = ? WHERE \"id\" = ?"
987 } else {
988 "UPDATE \"xprofiles\" SET (\"links\") = ($1) WHERE \"id\" = $2"
989 };
990
991 let c = &self.base.db.client;
992 let links = &serde_json::to_string(&links).unwrap();
993
994 match sqlquery(query)
995 .bind::<&str>(links)
996 .bind::<&str>(&id)
997 .execute(c)
998 .await
999 {
1000 Ok(_) => {
1001 self.base
1002 .cache
1003 .remove(format!("rbeam.auth.profile:{}", ua.username))
1004 .await;
1005
1006 self.base
1007 .cache
1008 .remove(format!("rbeam.auth.profile:{}", ua.id))
1009 .await;
1010
1011 Ok(())
1012 }
1013 Err(_) => Err(DatabaseError::Other),
1014 }
1015 }
1016
1017 pub async fn update_profile_layout(&self, id: &str, layout: LayoutComponent) -> Result<()> {
1019 let ua = match self.get_profile(&id).await {
1021 Ok(ua) => ua,
1022 Err(e) => return Err(e),
1023 };
1024
1025 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1027 "UPDATE \"xprofiles\" SET \"layout\" = ? WHERE \"id\" = ?"
1028 } else {
1029 "UPDATE \"xprofiles\" SET (\"layout\") = ($1) WHERE \"id\" = $2"
1030 };
1031
1032 let c = &self.base.db.client;
1033 let layout = &serde_json::to_string(&layout).unwrap();
1034
1035 match sqlquery(query)
1036 .bind::<&str>(layout)
1037 .bind::<&str>(&id)
1038 .execute(c)
1039 .await
1040 {
1041 Ok(_) => {
1042 self.base
1043 .cache
1044 .remove(format!("rbeam.auth.profile:{}", ua.username))
1045 .await;
1046
1047 self.base
1048 .cache
1049 .remove(format!("rbeam.auth.profile:{}", ua.id))
1050 .await;
1051
1052 Ok(())
1053 }
1054 Err(_) => Err(DatabaseError::Other),
1055 }
1056 }
1057
1058 pub async fn update_profile_tier(&self, id: &str, tier: i32) -> Result<()> {
1060 let ua = match self.get_profile(&id).await {
1062 Ok(ua) => ua,
1063 Err(e) => return Err(e),
1064 };
1065
1066 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1068 "UPDATE \"xprofiles\" SET \"tier\" = ? WHERE \"id\" = ?"
1069 } else {
1070 "UPDATE \"xprofiles\" SET (\"tier\") = ($1) WHERE \"id\" = $2"
1071 };
1072
1073 let c = &self.base.db.client;
1074 match sqlquery(query)
1075 .bind::<i32>(tier)
1076 .bind::<&str>(&id)
1077 .execute(c)
1078 .await
1079 {
1080 Ok(_) => {
1081 self.base
1082 .cache
1083 .remove(format!("rbeam.auth.profile:{}", ua.username))
1084 .await;
1085
1086 self.base
1087 .cache
1088 .remove(format!("rbeam.auth.profile:{}", ua.id))
1089 .await;
1090
1091 Ok(())
1092 }
1093 Err(_) => Err(DatabaseError::Other),
1094 }
1095 }
1096
1097 pub async fn update_profile_group(&self, id: &str, group: i32) -> Result<()> {
1099 let ua = match self.get_profile(&id).await {
1101 Ok(ua) => ua,
1102 Err(e) => return Err(e),
1103 };
1104
1105 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1107 "UPDATE \"xprofiles\" SET \"gid\" = ? WHERE \"id\" = ?"
1108 } else {
1109 "UPDATE \"xprofiles\" SET (\"gid\") = ($1) WHERE \"id\" = $2"
1110 };
1111
1112 let c = &self.base.db.client;
1113 match sqlquery(query)
1114 .bind::<&i32>(&group)
1115 .bind::<&str>(&id)
1116 .execute(c)
1117 .await
1118 {
1119 Ok(_) => {
1120 self.base
1121 .cache
1122 .remove(format!("rbeam.auth.profile:{}", ua.username))
1123 .await;
1124
1125 self.base
1126 .cache
1127 .remove(format!("rbeam.auth.profile:{}", ua.id))
1128 .await;
1129
1130 Ok(())
1131 }
1132 Err(_) => Err(DatabaseError::Other),
1133 }
1134 }
1135
1136 pub async fn update_profile_coins(&self, id: &str, coins: i32) -> Result<()> {
1141 let ua = match self.get_profile(&id).await {
1143 Ok(ua) => ua,
1144 Err(e) => return Err(e),
1145 };
1146
1147 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1149 "UPDATE \"xprofiles\" SET \"coins\" = ? WHERE \"id\" = ?"
1150 } else {
1151 "UPDATE \"xprofiles\" SET (\"coins\") = ($1) WHERE \"id\" = $2"
1152 };
1153
1154 let c = &self.base.db.client;
1155 match sqlquery(query)
1156 .bind::<i32>(ua.coins + coins)
1157 .bind::<&str>(&id)
1158 .execute(c)
1159 .await
1160 {
1161 Ok(_) => {
1162 self.base
1163 .cache
1164 .remove(format!("rbeam.auth.profile:{}", ua.username))
1165 .await;
1166
1167 self.base
1168 .cache
1169 .remove(format!("rbeam.auth.profile:{}", ua.id))
1170 .await;
1171
1172 Ok(())
1173 }
1174 Err(_) => Err(DatabaseError::Other),
1175 }
1176 }
1177
1178 pub async fn update_profile_password(
1180 &self,
1181 id: &str,
1182 password: &str,
1183 new_password: &str,
1184 do_password_check: bool,
1185 ) -> Result<()> {
1186 let ua = match self.get_profile(&id).await {
1188 Ok(ua) => ua,
1189 Err(e) => return Err(e),
1190 };
1191
1192 if do_password_check {
1194 let password_hashed = rainbeam_shared::hash::hash_salted(password.to_string(), ua.salt);
1195
1196 if password_hashed != ua.password {
1197 return Err(DatabaseError::NotAllowed);
1198 }
1199 }
1200
1201 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1203 "UPDATE \"xprofiles\" SET \"password\" = ?, \"salt\" = ? WHERE \"id\" = ?"
1204 } else {
1205 "UPDATE \"xprofiles\" SET (\"password\", \"salt\") = ($1, $2) WHERE \"id\" = $3"
1206 };
1207
1208 let new_salt = rainbeam_shared::hash::salt();
1209
1210 let c = &self.base.db.client;
1211 match sqlquery(query)
1212 .bind::<&str>(&rainbeam_shared::hash::hash_salted(
1213 new_password.to_string(),
1214 new_salt.clone(),
1215 ))
1216 .bind::<&str>(&new_salt)
1217 .bind::<&str>(&id)
1218 .execute(c)
1219 .await
1220 {
1221 Ok(_) => {
1222 self.base
1223 .cache
1224 .remove(format!("rbeam.auth.profile:{}", ua.username))
1225 .await;
1226
1227 self.base
1228 .cache
1229 .remove(format!("rbeam.auth.profile:{}", ua.id))
1230 .await;
1231
1232 Ok(())
1233 }
1234 Err(_) => Err(DatabaseError::Other),
1235 }
1236 }
1237
1238 pub async fn update_profile_username(
1240 &self,
1241 id: &str,
1242 password: &str,
1243 new_name: &str,
1244 ) -> Result<()> {
1245 let new_name = new_name.to_lowercase();
1246
1247 let ua = match self.get_profile(&id).await {
1249 Ok(ua) => ua,
1250 Err(e) => return Err(e),
1251 };
1252
1253 if let Ok(_) = self.get_profile_by_username(&new_name).await {
1255 return Err(DatabaseError::MustBeUnique);
1256 }
1257
1258 if let Err(e) = Database::validate_username(&new_name) {
1260 return Err(e);
1261 }
1262
1263 let password_hashed = rainbeam_shared::hash::hash_salted(password.to_string(), ua.salt);
1265
1266 if password_hashed != ua.password {
1267 return Err(DatabaseError::NotAllowed);
1268 }
1269
1270 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1272 "UPDATE \"xprofiles\" SET \"username\" = ? WHERE \"id\" = ?"
1273 } else {
1274 "UPDATE \"xprofiles\" SET (\"username\") = ($1) WHERE \"id\" = $2"
1275 };
1276
1277 let c = &self.base.db.client;
1278 match sqlquery(query)
1279 .bind::<&str>(&new_name)
1280 .bind::<&str>(&id)
1281 .execute(c)
1282 .await
1283 {
1284 Ok(_) => {
1285 self.base
1286 .cache
1287 .remove(format!("rbeam.auth.profile:{}", ua.username))
1288 .await;
1289
1290 self.base
1291 .cache
1292 .remove(format!("rbeam.auth.profile:{}", ua.id))
1293 .await;
1294
1295 Ok(())
1296 }
1297 Err(_) => Err(DatabaseError::Other),
1298 }
1299 }
1300
1301 async fn delete_profile(&self, id: &str) -> Result<()> {
1305 let user = self.get_profile_by_id(&id).await.unwrap();
1306
1307 let query: &str = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1309 "DELETE FROM \"xprofiles\" WHERE \"id\" = ?"
1310 } else {
1311 "DELETE FROM \"xprofiles\" WHERE \"id\" = $1"
1312 };
1313
1314 let c = &self.base.db.client;
1315 match sqlquery(query).bind::<&str>(&id).execute(c).await {
1316 Ok(_) => {
1317 let query: &str =
1318 if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1319 "DELETE FROM \"xnotifications\" WHERE \"recipient\" = ?"
1320 } else {
1321 "DELETE FROM \"xnotifications\" WHERE \"recipient\" = $1"
1322 };
1323
1324 if let Err(_) = sqlquery(query).bind::<&str>(&id).execute(c).await {
1325 return Err(DatabaseError::Other);
1326 };
1327
1328 let query: &str =
1329 if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1330 "DELETE FROM \"xwarnings\" WHERE \"recipient\" = ?"
1331 } else {
1332 "DELETE FROM \"xwarnings\" WHERE \"recipient\" = $1"
1333 };
1334
1335 if let Err(_) = sqlquery(query).bind::<&str>(&id).execute(c).await {
1336 return Err(DatabaseError::Other);
1337 };
1338
1339 let query: &str =
1340 if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1341 "DELETE FROM \"xfollows\" WHERE \"user\" = ? OR \"following\" = ?"
1342 } else {
1343 "DELETE FROM \"xfollows\" WHERE \"user\" = $1 OR \"following\" = $2"
1344 };
1345
1346 if let Err(_) = sqlquery(query)
1347 .bind::<&str>(&id)
1348 .bind::<&str>(&id)
1349 .execute(c)
1350 .await
1351 {
1352 return Err(DatabaseError::Other);
1353 };
1354
1355 let query: &str =
1358 if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1359 "DELETE FROM \"xquestions\" WHERE \"recipient\" = ?"
1360 } else {
1361 "DELETE FROM \"xquestions\" WHERE \"recipient\" = $1"
1362 };
1363
1364 if let Err(_) = sqlquery(query).bind::<&str>(&id).execute(c).await {
1365 return Err(DatabaseError::Other);
1366 };
1367
1368 let query: &str =
1370 if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1371 "DELETE FROM \"xquestions\" WHERE \"author\" = ?"
1372 } else {
1373 "DELETE FROM \"xquestions\" WHERE \"author\" = $1"
1374 };
1375
1376 if let Err(_) = sqlquery(query).bind::<&str>(&id).execute(c).await {
1377 return Err(DatabaseError::Other);
1378 };
1379
1380 let query: &str =
1382 if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1383 "DELETE FROM \"xresponses\" WHERE \"author\" = ?"
1384 } else {
1385 "DELETE FROM \"xresponses\" WHERE \"author\" = $1"
1386 };
1387
1388 if let Err(_) = sqlquery(query).bind::<&str>(&id).execute(c).await {
1389 return Err(DatabaseError::Other);
1390 };
1391
1392 let query: &str =
1394 if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1395 "DELETE FROM \"xresponses\" WHERE \"question\" LIKE ?"
1396 } else {
1397 "DELETE FROM \"xresponses\" WHERE \"question\" LIKE $1"
1398 };
1399
1400 if let Err(_) = sqlquery(query)
1401 .bind::<&str>(&format!("%\"author\":\"{id}\"%"))
1402 .execute(c)
1403 .await
1404 {
1405 return Err(DatabaseError::Other);
1406 };
1407
1408 self.base
1409 .cache
1410 .remove(format!("rbeam.app.response_count:{}", id))
1411 .await;
1412
1413 self.base
1414 .cache
1415 .remove(format!("rbeam.app.global_question_count:{}", id))
1416 .await;
1417
1418 let query: &str =
1420 if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1421 "DELETE FROM \"xrelationships\" WHERE \"one\" = ? OR \"two\" = ?"
1422 } else {
1423 "DELETE FROM \"xrelationships\" WHERE \"one\" = $1 OR \"two\" = $2"
1424 };
1425
1426 if let Err(_) = sqlquery(query)
1427 .bind::<&str>(&id)
1428 .bind::<&str>(&id)
1429 .execute(c)
1430 .await
1431 {
1432 return Err(DatabaseError::Other);
1433 };
1434
1435 self.base
1436 .cache
1437 .remove(format!("rbeam.app.friends_count:{}", id))
1438 .await;
1439
1440 let query: &str =
1442 if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1443 "DELETE FROM \"xipblocks\" WHERE \"user\" = ?"
1444 } else {
1445 "DELETE FROM \"xipblocks\" WHERE \"user\" = $1"
1446 };
1447
1448 if let Err(_) = sqlquery(query).bind::<&str>(&id).execute(c).await {
1449 return Err(DatabaseError::Other);
1450 };
1451
1452 let query: &str = if (self.base.db.r#type == "sqlite")
1454 | (self.base.db.r#type == "mysql")
1455 {
1456 "DELETE FROM \"xugc_transactions\" WHERE \"customer\" = ? OR \"merchant\" = ?"
1457 } else {
1458 "DELETE FROM \"xugc_transactions\" WHERE \"customer\" = $1 OR \"merchant\" = $2"
1459 };
1460
1461 if let Err(_) = sqlquery(query)
1462 .bind::<&str>(&id)
1463 .bind::<&str>(&id)
1464 .execute(c)
1465 .await
1466 {
1467 return Err(DatabaseError::Other);
1468 };
1469
1470 let query: &str =
1472 if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1473 "DELETE FROM \"xugc_items\" WHERE \"creator\" = ?"
1474 } else {
1475 "DELETE FROM \"xugc_items\" WHERE \"creator\" = $1"
1476 };
1477
1478 if let Err(_) = sqlquery(query).bind::<&str>(&id).execute(c).await {
1479 return Err(DatabaseError::Other);
1480 };
1481
1482 self.base
1484 .cache
1485 .remove(format!("rbeam.auth.profile:{}", id))
1486 .await;
1487
1488 self.base
1489 .cache
1490 .remove(format!("rbeam.auth.profile:{}", user.username))
1491 .await;
1492
1493 self.base
1494 .cache
1495 .remove(format!("rbeam.auth.followers_count:{}", id))
1496 .await;
1497
1498 self.base
1499 .cache
1500 .remove(format!("rbeam.auth.following_count:{}", id))
1501 .await;
1502
1503 self.base
1504 .cache
1505 .remove(format!("rbeam.auth.notification_count:{}", id))
1506 .await;
1507
1508 if !self.config.media_dir.to_string().is_empty() {
1510 let avatar = pathd!("{}/avatars/{}.avif", self.config.media_dir, id);
1511 if let Ok(_) = rainbeam_shared::fs::fstat(&avatar) {
1512 if let Err(_) = rainbeam_shared::fs::remove_file(avatar) {
1513 return Err(DatabaseError::Other);
1514 }
1515 }
1516
1517 let banner = pathd!("{}/banners/{}.avif", self.config.media_dir, id);
1518 if let Ok(_) = rainbeam_shared::fs::fstat(&banner) {
1519 if let Err(_) = rainbeam_shared::fs::remove_file(banner) {
1520 return Err(DatabaseError::Other);
1521 }
1522 }
1523 }
1524
1525 Ok(())
1527 }
1528 Err(_) => Err(DatabaseError::Other),
1529 }
1530 }
1531
1532 pub async fn delete_profile_by_id(&self, id: &str) -> Result<()> {
1534 let user = match self.get_profile_by_id(&id).await {
1535 Ok(ua) => ua,
1536 Err(e) => return Err(e),
1537 };
1538
1539 let group = match self.get_group_by_id(user.group).await {
1541 Ok(g) => g,
1542 Err(_) => return Err(DatabaseError::Other),
1543 };
1544
1545 if group.permissions.check(FinePermission::DELETE_USER) {
1546 return Err(DatabaseError::NotAllowed);
1547 }
1548
1549 self.delete_profile(id).await
1551 }
1552
1553 pub async fn get_group_by_id(&self, id: i32) -> Result<Group> {
1561 let cached = self.base.cache.get(format!("rbeam.auth.gid:{}", id)).await;
1563
1564 if cached.is_some() {
1565 return Ok(serde_json::from_str::<Group>(cached.unwrap().as_str()).unwrap());
1566 }
1567
1568 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1570 "SELECT * FROM \"xgroups\" WHERE \"id\" = ?"
1571 } else {
1572 "SELECT * FROM \"xgroups\" WHERE \"id\" = $1"
1573 };
1574
1575 let c = &self.base.db.client;
1576 let row = match sqlquery(query).bind::<&i32>(&id).fetch_one(c).await {
1577 Ok(r) => self.base.textify_row(r).0,
1578 Err(_) => return Ok(Group::default()),
1579 };
1580
1581 let group = Group {
1583 name: from_row!(row->name()),
1584 id: row.get("id").unwrap().parse::<i32>().unwrap(),
1585 permissions: match serde_json::from_str(row.get("permissions").unwrap()) {
1586 Ok(m) => m,
1587 Err(_) => return Err(DatabaseError::ValueError),
1588 },
1589 };
1590
1591 self.base
1592 .cache
1593 .set(
1594 format!("rbeam.auth.gid:{}", id),
1595 serde_json::to_string::<Group>(&group).unwrap(),
1596 )
1597 .await;
1598
1599 Ok(group)
1601 }
1602
1603 pub async fn get_follow(&self, user: &str, following: &str) -> Result<UserFollow> {
1612 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1614 "SELECT * FROM \"xfollows\" WHERE \"user\" = ? AND \"following\" = ?"
1615 } else {
1616 "SELECT * FROM \"xfollows\" WHERE \"user\" = $1 AND \"following\" = $2"
1617 };
1618
1619 let c = &self.base.db.client;
1620 let row = match sqlquery(query)
1621 .bind::<&str>(&user)
1622 .bind::<&str>(&following)
1623 .fetch_one(c)
1624 .await
1625 {
1626 Ok(u) => self.base.textify_row(u).0,
1627 Err(_) => return Err(DatabaseError::Other),
1628 };
1629
1630 Ok(UserFollow {
1632 user: from_row!(row->user()),
1633 following: from_row!(row->following()),
1634 })
1635 }
1636
1637 pub async fn get_followers(
1642 &self,
1643 user: &str,
1644 ) -> Result<Vec<(UserFollow, Box<Profile>, Box<Profile>)>> {
1645 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1647 "SELECT * FROM \"xfollows\" WHERE \"following\" = ?"
1648 } else {
1649 "SELECT * FROM \"xfollows\" WHERE \"following\" = $1"
1650 };
1651
1652 let c = &self.base.db.client;
1653 let res = match sqlquery(query).bind::<&str>(&user).fetch_all(c).await {
1654 Ok(u) => {
1655 let mut out = Vec::new();
1656
1657 for row in u {
1658 let row = self.base.textify_row(row).0;
1659
1660 let user = from_row!(row->user());
1661 let following = from_row!(row->following());
1662
1663 out.push((
1664 UserFollow {
1665 user: user.clone(),
1666 following: following.clone(),
1667 },
1668 match self.get_profile_by_id(&user).await {
1669 Ok(ua) => ua,
1670 Err(e) => return Err(e),
1671 },
1672 match self.get_profile_by_id(&following).await {
1673 Ok(ua) => ua,
1674 Err(e) => return Err(e),
1675 },
1676 ))
1677 }
1678
1679 out
1680 }
1681 Err(_) => return Err(DatabaseError::Other),
1682 };
1683
1684 Ok(res)
1686 }
1687
1688 pub async fn get_followers_paginated(
1694 &self,
1695 user: &str,
1696 page: i32,
1697 ) -> Result<Vec<(UserFollow, Box<Profile>, Box<Profile>)>> {
1698 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1700 format!(
1701 "SELECT * FROM \"xfollows\" WHERE \"following\" = ? LIMIT 12 OFFSET {}",
1702 page * 12
1703 )
1704 } else {
1705 format!(
1706 "SELECT * FROM \"xfollows\" WHERE \"following\" = $1 LIMIT 12 OFFSET {}",
1707 page * 12
1708 )
1709 };
1710
1711 let c = &self.base.db.client;
1712 let res = match sqlquery(&query).bind::<&str>(&user).fetch_all(c).await {
1713 Ok(u) => {
1714 let mut out = Vec::new();
1715
1716 for row in u {
1717 let row = self.base.textify_row(row).0;
1718
1719 let user = from_row!(row->user());
1720 let following = from_row!(row->following());
1721
1722 out.push((
1723 UserFollow {
1724 user: user.clone(),
1725 following: following.clone(),
1726 },
1727 match self.get_profile_by_id(&user).await {
1728 Ok(ua) => ua,
1729 Err(_) => continue,
1730 },
1731 match self.get_profile_by_id(&following).await {
1732 Ok(ua) => ua,
1733 Err(_) => continue,
1734 },
1735 ))
1736 }
1737
1738 out
1739 }
1740 Err(_) => return Err(DatabaseError::Other),
1741 };
1742
1743 Ok(res)
1745 }
1746
1747 pub async fn get_followers_count(&self, user: &str) -> usize {
1752 if let Some(count) = self
1754 .base
1755 .cache
1756 .get(format!("rbeam.auth.followers_count:{}", user))
1757 .await
1758 {
1759 return count.parse::<usize>().unwrap_or(0);
1760 };
1761
1762 let count = self.get_followers(user).await.unwrap_or(Vec::new()).len();
1764
1765 self.base
1766 .cache
1767 .set(
1768 format!("rbeam.auth.followers_count:{}", user),
1769 count.to_string(),
1770 )
1771 .await;
1772
1773 count
1774 }
1775
1776 pub async fn get_following(
1781 &self,
1782 user: &str,
1783 ) -> Result<Vec<(UserFollow, Box<Profile>, Box<Profile>)>> {
1784 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1786 "SELECT * FROM \"xfollows\" WHERE \"user\" = ?"
1787 } else {
1788 "SELECT * FROM \"xfollows\" WHERE \"user\" = $1"
1789 };
1790
1791 let c = &self.base.db.client;
1792 let res = match sqlquery(query).bind::<&str>(&user).fetch_all(c).await {
1793 Ok(u) => {
1794 let mut out = Vec::new();
1795
1796 for row in u {
1797 let row = self.base.textify_row(row).0;
1798
1799 let user = from_row!(row->user());
1800 let following = from_row!(row->following());
1801
1802 out.push((
1803 UserFollow {
1804 user: user.clone(),
1805 following: following.clone(),
1806 },
1807 match self.get_profile_by_id(&user).await {
1808 Ok(ua) => ua,
1809 Err(_) => continue,
1810 },
1811 match self.get_profile_by_id(&following).await {
1812 Ok(ua) => ua,
1813 Err(_) => continue,
1814 },
1815 ))
1816 }
1817
1818 out
1819 }
1820 Err(_) => return Err(DatabaseError::Other),
1821 };
1822
1823 Ok(res)
1825 }
1826
1827 pub async fn get_following_paginated(
1833 &self,
1834 user: &str,
1835 page: i32,
1836 ) -> Result<Vec<(UserFollow, Box<Profile>, Box<Profile>)>> {
1837 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1839 format!(
1840 "SELECT * FROM \"xfollows\" WHERE \"user\" = ? LIMIT 12 OFFSET {}",
1841 page * 12
1842 )
1843 } else {
1844 format!(
1845 "SELECT * FROM \"xfollows\" WHERE \"user\" = $1 LIMIT 12 OFFSET {}",
1846 page * 12
1847 )
1848 };
1849
1850 let c = &self.base.db.client;
1851 let res = match sqlquery(&query).bind::<&str>(&user).fetch_all(c).await {
1852 Ok(u) => {
1853 let mut out = Vec::new();
1854
1855 for row in u {
1856 let row = self.base.textify_row(row).0;
1857
1858 let user = from_row!(row->user());
1859 let following = from_row!(row->following());
1860
1861 out.push((
1862 UserFollow {
1863 user: user.clone(),
1864 following: following.clone(),
1865 },
1866 match self.get_profile_by_id(&user).await {
1867 Ok(ua) => ua,
1868 Err(_) => continue,
1869 },
1870 match self.get_profile_by_id(&following).await {
1871 Ok(ua) => ua,
1872 Err(_) => continue,
1873 },
1874 ))
1875 }
1876
1877 out
1878 }
1879 Err(_) => return Err(DatabaseError::Other),
1880 };
1881
1882 Ok(res)
1884 }
1885
1886 pub async fn get_following_count(&self, user: &str) -> usize {
1891 if let Some(count) = self
1893 .base
1894 .cache
1895 .get(format!("rbeam.auth.following_count:{}", user))
1896 .await
1897 {
1898 return count.parse::<usize>().unwrap_or(0);
1899 };
1900
1901 let count = self.get_following(user).await.unwrap_or(Vec::new()).len();
1903
1904 self.base
1905 .cache
1906 .set(
1907 format!("rbeam.auth.following_count:{}", user),
1908 count.to_string(),
1909 )
1910 .await;
1911
1912 count
1913 }
1914
1915 pub async fn toggle_user_follow(&self, props: &mut UserFollow) -> Result<()> {
1921 if props.user == props.following {
1923 return Err(DatabaseError::Other);
1924 }
1925
1926 let user_1 = match self.get_profile(&props.user).await {
1928 Ok(ua) => ua,
1929 Err(e) => return Err(e),
1930 };
1931
1932 if let Err(e) = self.get_profile(&props.following).await {
1934 return Err(e);
1935 };
1936
1937 if let Ok(_) = self.get_follow(&props.user, &props.following).await {
1939 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1941 "DELETE FROM \"xfollows\" WHERE \"user\" = ? AND \"following\" = ?"
1942 } else {
1943 "DELETE FROM \"xfollows\" WHERE \"user\" = $1 AND \"following\" = $2"
1944 };
1945
1946 let c = &self.base.db.client;
1947 match sqlquery(&query)
1948 .bind::<&str>(&props.user)
1949 .bind::<&str>(&props.following)
1950 .execute(c)
1951 .await
1952 {
1953 Ok(_) => {
1954 self.base
1955 .cache
1956 .decr(format!("rbeam.auth.following_count:{}", props.user))
1957 .await;
1958
1959 self.base
1960 .cache
1961 .decr(format!("rbeam.auth.followers_count:{}", props.following))
1962 .await;
1963
1964 return Ok(());
1965 }
1966 Err(_) => return Err(DatabaseError::Other),
1967 };
1968 }
1969
1970 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
1972 "INSERT INTO \"xfollows\" VALUES (?, ?)"
1973 } else {
1974 "INSERT INTO \"xfollows\" VALUES ($1, $2)"
1975 };
1976
1977 let c = &self.base.db.client;
1978 match sqlquery(&query)
1979 .bind::<&str>(&props.user)
1980 .bind::<&str>(&props.following)
1981 .execute(c)
1982 .await
1983 {
1984 Ok(_) => {
1985 self.base
1987 .cache
1988 .incr(format!("rbeam.auth.following_count:{}", props.user))
1989 .await;
1990
1991 self.base
1992 .cache
1993 .incr(format!("rbeam.auth.followers_count:{}", props.following))
1994 .await;
1995
1996 if let Err(e) = self
1998 .create_notification(
1999 NotificationCreate {
2000 title: format!(
2001 "[@{}](/+u/{}) followed you!",
2002 user_1.username, user_1.id
2003 ),
2004 content: String::new(),
2005 address: format!("/+u/{}", user_1.id),
2006 recipient: props.following.clone(),
2007 },
2008 None,
2009 )
2010 .await
2011 {
2012 return Err(e);
2013 };
2014
2015 Ok(())
2017 }
2018 Err(_) => Err(DatabaseError::Other),
2019 }
2020 }
2021
2022 pub async fn force_remove_user_follow(&self, props: &mut UserFollow) -> Result<()> {
2027 if props.user == props.following {
2029 return Err(DatabaseError::Other);
2030 }
2031
2032 if let Ok(_) = self.get_follow(&props.user, &props.following).await {
2034 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
2036 "DELETE FROM \"xfollows\" WHERE \"user\" = ? AND \"following\" = ?"
2037 } else {
2038 "DELETE FROM \"xfollows\" WHERE \"user\" = $1 AND \"following\" = $2"
2039 };
2040
2041 let c = &self.base.db.client;
2042 match sqlquery(&query)
2043 .bind::<&str>(&props.user)
2044 .bind::<&str>(&props.following)
2045 .execute(c)
2046 .await
2047 {
2048 Ok(_) => {
2049 self.base
2050 .cache
2051 .decr(format!("rbeam.auth.following_count:{}", props.user))
2052 .await;
2053
2054 self.base
2055 .cache
2056 .decr(format!("rbeam.auth.followers_count:{}", props.following))
2057 .await;
2058
2059 return Ok(());
2060 }
2061 Err(_) => return Err(DatabaseError::Other),
2062 };
2063 }
2064
2065 Ok(())
2068 }
2069
2070 pub async fn get_notification(&self, id: &str) -> Result<Notification> {
2078 match self
2080 .base
2081 .cache
2082 .get(format!("rbeam.auth.notification:{}", id))
2083 .await
2084 {
2085 Some(c) => return Ok(serde_json::from_str::<Notification>(c.as_str()).unwrap()),
2086 None => (),
2087 };
2088
2089 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
2091 "SELECT * FROM \"xnotifications\" WHERE \"id\" = ?"
2092 } else {
2093 "SELECT * FROM \"xnotifications\" WHERE \"id\" = $1"
2094 };
2095
2096 let c = &self.base.db.client;
2097 let res = match sqlquery(&query).bind::<&str>(&id).fetch_one(c).await {
2098 Ok(p) => self.base.textify_row(p).0,
2099 Err(_) => return Err(DatabaseError::NotFound),
2100 };
2101
2102 let notification = Notification {
2104 title: from_row!(res->title()),
2105 content: from_row!(res->content()),
2106 address: from_row!(res->address()),
2107 timestamp: from_row!(res->timestamp(u128); 0),
2108 id: from_row!(res->id()),
2109 recipient: from_row!(res->recipient()),
2110 };
2111
2112 self.base
2114 .cache
2115 .set(
2116 format!("rbeam.auth.notification:{}", id),
2117 serde_json::to_string::<Notification>(¬ification).unwrap(),
2118 )
2119 .await;
2120
2121 Ok(notification)
2123 }
2124
2125 pub async fn get_notifications_by_recipient(
2130 &self,
2131 recipient: &str,
2132 ) -> Result<Vec<Notification>> {
2133 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
2135 "SELECT * FROM \"xnotifications\" WHERE \"recipient\" = ? ORDER BY \"timestamp\" DESC"
2136 } else {
2137 "SELECT * FROM \"xnotifications\" WHERE \"recipient\" = $1 ORDER BY \"timestamp\" DESC"
2138 };
2139
2140 let c = &self.base.db.client;
2141 let res = match sqlquery(&query)
2142 .bind::<&str>(&recipient.to_lowercase())
2143 .fetch_all(c)
2144 .await
2145 {
2146 Ok(p) => {
2147 let mut out: Vec<Notification> = Vec::new();
2148
2149 for row in p {
2150 let res = self.base.textify_row(row).0;
2151 out.push(Notification {
2152 title: from_row!(res->title()),
2153 content: from_row!(res->content()),
2154 address: from_row!(res->address()),
2155 timestamp: from_row!(res->timestamp(u128); 0),
2156 id: from_row!(res->id()),
2157 recipient: from_row!(res->recipient()),
2158 });
2159 }
2160
2161 out
2162 }
2163 Err(_) => return Err(DatabaseError::NotFound),
2164 };
2165
2166 Ok(res)
2168 }
2169
2170 pub async fn get_notification_count_by_recipient_cache(&self, recipient: &str) -> usize {
2175 if let Some(count) = self
2177 .base
2178 .cache
2179 .get(format!("rbeam.auth.notification_count:{}", recipient))
2180 .await
2181 {
2182 return count.parse::<usize>().unwrap_or(0);
2183 };
2184
2185 let count = self
2187 .get_notifications_by_recipient(recipient)
2188 .await
2189 .unwrap_or(Vec::new())
2190 .len();
2191
2192 self.base
2193 .cache
2194 .set(
2195 format!("rbeam.auth.notification_count:{}", recipient),
2196 count.to_string(),
2197 )
2198 .await;
2199
2200 count
2201 }
2202
2203 pub async fn get_notification_count_by_recipient(&self, recipient: &str) -> usize {
2208 match self.get_profile(recipient).await {
2209 Ok(x) => x.notification_count,
2210 Err(_) => 0,
2211 }
2212 }
2213
2214 pub async fn get_notifications_by_recipient_paginated(
2220 &self,
2221 recipient: &str,
2222 page: i32,
2223 ) -> Result<Vec<Notification>> {
2224 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
2226 format!("SELECT * FROM \"xnotifications\" WHERE \"recipient\" = ? ORDER BY \"timestamp\" DESC LIMIT 12 OFFSET {}", page * 12)
2227 } else {
2228 format!("SELECT * FROM \"xnotifications\" WHERE \"recipient\" = $1 ORDER BY \"timestamp\" DESC LIMIT 12 OFFSET {}", page * 12)
2229 };
2230
2231 let c = &self.base.db.client;
2232 let res = match sqlquery(&query)
2233 .bind::<&str>(&recipient.to_lowercase())
2234 .fetch_all(c)
2235 .await
2236 {
2237 Ok(p) => {
2238 let mut out: Vec<Notification> = Vec::new();
2239
2240 for row in p {
2241 let res = self.base.textify_row(row).0;
2242 out.push(Notification {
2243 title: from_row!(res->title()),
2244 content: from_row!(res->content()),
2245 address: from_row!(res->address()),
2246 timestamp: from_row!(res->timestamp(u128); 0),
2247 id: from_row!(res->id()),
2248 recipient: from_row!(res->recipient()),
2249 });
2250 }
2251
2252 out
2253 }
2254 Err(_) => return Err(DatabaseError::NotFound),
2255 };
2256
2257 Ok(res)
2259 }
2260
2261 pub async fn create_notification(
2267 &self,
2268 props: NotificationCreate,
2269 id: Option<String>,
2270 ) -> Result<()> {
2271 let notification = Notification {
2272 title: props.title,
2273 content: props.content,
2274 address: props.address,
2275 timestamp: utility::unix_epoch_timestamp(),
2276 id: if let Some(id) = id {
2277 id
2278 } else {
2279 AlmostSnowflake::new(self.config.snowflake_server_id).to_string()
2280 },
2281 recipient: props.recipient,
2282 };
2283
2284 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
2286 "INSERT INTO \"xnotifications\" VALUES (?, ?, ?, ?, ?, ?)"
2287 } else {
2288 "INSERT INTO \"xnotifications\" VALUES ($1, $2, $3, $4, $5, $6)"
2289 };
2290
2291 let c = &self.base.db.client;
2292 match sqlquery(&query)
2293 .bind::<&str>(¬ification.title)
2294 .bind::<&str>(¬ification.content)
2295 .bind::<&str>(¬ification.address)
2296 .bind::<&str>(¬ification.timestamp.to_string())
2297 .bind::<&str>(¬ification.id)
2298 .bind::<&str>(¬ification.recipient)
2299 .execute(c)
2300 .await
2301 {
2302 Ok(_) => {
2303 self.base
2305 .cache
2306 .incr(format!(
2307 "rbeam.auth.notification_count:{}",
2308 notification.recipient
2309 ))
2310 .await;
2311
2312 if !notification.recipient.starts_with("*") {
2314 let recipient =
2315 simplify!(self.get_profile(¬ification.recipient).await; Result);
2316
2317 simplify!(
2318 self.update_profile_notification_count(
2319 ¬ification.recipient,
2320 recipient.notification_count + 1,
2321 )
2322 .await; Err
2323 );
2324 }
2325
2326 return Ok(());
2328 }
2329 Err(_) => return Err(DatabaseError::Other),
2330 };
2331 }
2332
2333 pub async fn delete_notification(&self, id: &str, user: Box<Profile>) -> Result<()> {
2341 let notification = match self.get_notification(id).await {
2343 Ok(n) => n,
2344 Err(e) => return Err(e),
2345 };
2346
2347 if user.id != notification.recipient {
2349 let group = match self.get_group_by_id(user.group).await {
2351 Ok(g) => g,
2352 Err(_) => return Err(DatabaseError::Other),
2353 };
2354
2355 if !group
2356 .permissions
2357 .check(FinePermission::MANAGE_NOTIFICATIONS)
2358 {
2359 return Err(DatabaseError::NotAllowed);
2360 }
2361 }
2362
2363 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
2365 "DELETE FROM \"xnotifications\" WHERE \"id\" = ?"
2366 } else {
2367 "DELETE FROM \"xnotifications\" WHERE \"id\" = $1"
2368 };
2369
2370 let c = &self.base.db.client;
2371 match sqlquery(&query).bind::<&str>(&id).execute(c).await {
2372 Ok(_) => {
2373 self.base
2375 .cache
2376 .decr(format!(
2377 "rbeam.auth.notification_count:{}",
2378 notification.recipient
2379 ))
2380 .await;
2381
2382 self.base
2384 .cache
2385 .remove(format!("rbeam.auth.notification:{}", id))
2386 .await;
2387
2388 return Ok(());
2390 }
2391 Err(_) => return Err(DatabaseError::Other),
2392 };
2393 }
2394
2395 pub async fn delete_notifications_by_recipient(
2401 &self,
2402 recipient: &str,
2403 user: Box<Profile>,
2404 ) -> Result<()> {
2405 let notifications = match self.get_notifications_by_recipient(recipient).await {
2407 Ok(n) => n,
2408 Err(e) => return Err(e),
2409 };
2410
2411 if user.id != recipient {
2413 let group = match self.get_group_by_id(user.group).await {
2415 Ok(g) => g,
2416 Err(_) => return Err(DatabaseError::Other),
2417 };
2418
2419 if !group
2420 .permissions
2421 .check(FinePermission::MANAGE_NOTIFICATIONS)
2422 {
2423 return Err(DatabaseError::NotAllowed);
2424 }
2425 }
2426
2427 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
2429 "DELETE FROM \"xnotifications\" WHERE \"recipient\" = ?"
2430 } else {
2431 "DELETE FROM \"xnotifications\" WHERE \"recipient\" = $1"
2432 };
2433
2434 let c = &self.base.db.client;
2435 match sqlquery(&query).bind::<&str>(&recipient).execute(c).await {
2436 Ok(_) => {
2437 self.base
2439 .cache
2440 .remove(format!("rbeam.auth.notification_count:{}", recipient))
2441 .await;
2442
2443 for notification in notifications {
2445 self.base
2447 .cache
2448 .remove(format!("rbeam.auth.notification:{}", notification.id))
2449 .await;
2450 }
2451
2452 return Ok(());
2454 }
2455 Err(_) => return Err(DatabaseError::Other),
2456 };
2457 }
2458
2459 pub async fn get_warning(&self, id: &str) -> Result<Warning> {
2467 match self
2469 .base
2470 .cache
2471 .get(format!("rbeam.auth.warning:{}", id))
2472 .await
2473 {
2474 Some(c) => return Ok(serde_json::from_str::<Warning>(c.as_str()).unwrap()),
2475 None => (),
2476 };
2477
2478 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
2480 "SELECT * FROM \"xwarnings\" WHERE \"id\" = ?"
2481 } else {
2482 "SELECT * FROM \"xwarnings\" WHERE \"id\" = $1"
2483 };
2484
2485 let c = &self.base.db.client;
2486 let res = match sqlquery(&query).bind::<&str>(&id).fetch_one(c).await {
2487 Ok(p) => self.base.textify_row(p).0,
2488 Err(_) => return Err(DatabaseError::NotFound),
2489 };
2490
2491 let warning = Warning {
2493 id: from_row!(res->id()),
2494 content: from_row!(res->content()),
2495 timestamp: from_row!(res->timestamp(u128); 0),
2496 recipient: from_row!(res->recipient()),
2497 moderator: match self.get_profile_by_id(res.get("moderator").unwrap()).await {
2498 Ok(ua) => ua,
2499 Err(e) => return Err(e),
2500 },
2501 };
2502
2503 self.base
2505 .cache
2506 .set(
2507 format!("rbeam.auth.warning:{}", id),
2508 serde_json::to_string::<Warning>(&warning).unwrap(),
2509 )
2510 .await;
2511
2512 Ok(warning)
2514 }
2515
2516 pub async fn get_warnings_by_recipient(
2522 &self,
2523 recipient: &str,
2524 user: Box<Profile>,
2525 ) -> Result<Vec<Warning>> {
2526 let group = match self.get_group_by_id(user.group).await {
2528 Ok(g) => g,
2529 Err(_) => return Err(DatabaseError::Other),
2530 };
2531
2532 if !group.permissions.check(FinePermission::MANAGE_WARNINGS) {
2533 return Err(DatabaseError::NotAllowed);
2534 }
2535
2536 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
2538 "SELECT * FROM \"xwarnings\" WHERE \"recipient\" = ? ORDER BY \"timestamp\" DESC"
2539 } else {
2540 "SELECT * FROM \"xwarnings\" WHERE \"recipient\" = $1 ORDER BY \"timestamp\" DESC"
2541 };
2542
2543 let c = &self.base.db.client;
2544 let res = match sqlquery(&query)
2545 .bind::<&str>(&recipient.to_lowercase())
2546 .fetch_all(c)
2547 .await
2548 {
2549 Ok(p) => {
2550 let mut out: Vec<Warning> = Vec::new();
2551
2552 for row in p {
2553 let res = self.base.textify_row(row).0;
2554 out.push(Warning {
2555 id: from_row!(res->id()),
2556 content: from_row!(res->content()),
2557 timestamp: from_row!(res->timestamp(u128); 0),
2558 recipient: from_row!(res->recipient()),
2559 moderator: match self.get_profile_by_id(res.get("moderator").unwrap()).await
2560 {
2561 Ok(ua) => ua,
2562 Err(_) => continue,
2563 },
2564 });
2565 }
2566
2567 out
2568 }
2569 Err(_) => return Err(DatabaseError::NotFound),
2570 };
2571
2572 Ok(res)
2574 }
2575
2576 pub async fn create_warning(&self, props: WarningCreate, user: Box<Profile>) -> Result<()> {
2583 let group = match self.get_group_by_id(user.group).await {
2585 Ok(g) => g,
2586 Err(_) => return Err(DatabaseError::Other),
2587 };
2588
2589 if !group.permissions.check(FinePermission::MANAGE_WARNINGS) {
2590 return Err(DatabaseError::NotAllowed);
2591 }
2592
2593 let warning = Warning {
2595 id: utility::random_id(),
2596 content: props.content,
2597 timestamp: utility::unix_epoch_timestamp(),
2598 recipient: props.recipient,
2599 moderator: user,
2600 };
2601
2602 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
2604 "INSERT INTO \"xwarnings\" VALUES (?, ?, ?, ?, ?)"
2605 } else {
2606 "INSERT INTO \"xwarnings\" VALUES ($1, $2, $3, $4, $5)"
2607 };
2608
2609 let c = &self.base.db.client;
2610 match sqlquery(&query)
2611 .bind::<&str>(&warning.id)
2612 .bind::<&str>(&warning.content)
2613 .bind::<&str>(&warning.timestamp.to_string())
2614 .bind::<&str>(&warning.recipient)
2615 .bind::<&str>(&warning.moderator.id)
2616 .execute(c)
2617 .await
2618 {
2619 Ok(_) => {
2620 if let Err(e) = self
2622 .create_notification(
2623 NotificationCreate {
2624 title: "You have received an account warning!".to_string(),
2625 content: warning.content,
2626 address: String::new(),
2627 recipient: warning.recipient,
2628 },
2629 None,
2630 )
2631 .await
2632 {
2633 return Err(e);
2634 };
2635
2636 return Ok(());
2638 }
2639 Err(_) => return Err(DatabaseError::Other),
2640 };
2641 }
2642
2643 pub async fn delete_warning(&self, id: &str, user: Box<Profile>) -> Result<()> {
2651 let warning = match self.get_warning(id).await {
2653 Ok(n) => n,
2654 Err(e) => return Err(e),
2655 };
2656
2657 if user.id != warning.moderator.id {
2659 let group = match self.get_group_by_id(user.group).await {
2661 Ok(g) => g,
2662 Err(_) => return Err(DatabaseError::Other),
2663 };
2664
2665 if !group.permissions.check(FinePermission::MANAGE_WARNINGS) {
2666 return Err(DatabaseError::NotAllowed);
2667 }
2668 }
2669
2670 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
2672 "DELETE FROM \"xwarnings\" WHERE \"id\" = ?"
2673 } else {
2674 "DELETE FROM \"xwarnings\" WHERE \"id\" = $1"
2675 };
2676
2677 let c = &self.base.db.client;
2678 match sqlquery(&query).bind::<&str>(&id).execute(c).await {
2679 Ok(_) => {
2680 self.base
2682 .cache
2683 .remove(format!("rbeam.auth.warning:{}", id))
2684 .await;
2685
2686 return Ok(());
2688 }
2689 Err(_) => return Err(DatabaseError::Other),
2690 };
2691 }
2692
2693 pub async fn get_ipban(&self, id: &str) -> Result<IpBan> {
2701 match self
2703 .base
2704 .cache
2705 .get(format!("rbeam.auth.ipban:{}", id))
2706 .await
2707 {
2708 Some(c) => return Ok(serde_json::from_str::<IpBan>(c.as_str()).unwrap()),
2709 None => (),
2710 };
2711
2712 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
2714 "SELECT * FROM \"xbans\" WHERE \"id\" = ?"
2715 } else {
2716 "SELECT * FROM \"xbans\" WHERE \"id\" = $1"
2717 };
2718
2719 let c = &self.base.db.client;
2720 let res = match sqlquery(&query).bind::<&str>(&id).fetch_one(c).await {
2721 Ok(p) => self.base.textify_row(p).0,
2722 Err(_) => return Err(DatabaseError::NotFound),
2723 };
2724
2725 let ban = IpBan {
2727 id: from_row!(res->id()),
2728 ip: from_row!(res->ip()),
2729 reason: from_row!(res->reason()),
2730 moderator: match self.get_profile_by_id(res.get("moderator").unwrap()).await {
2731 Ok(ua) => ua,
2732 Err(e) => return Err(e),
2733 },
2734 timestamp: from_row!(res->timestamp(u128); 0),
2735 };
2736
2737 self.base
2739 .cache
2740 .set(
2741 format!("rbeam.auth.ipban:{}", id),
2742 serde_json::to_string::<IpBan>(&ban).unwrap(),
2743 )
2744 .await;
2745
2746 Ok(ban)
2748 }
2749
2750 pub async fn get_ipban_by_ip(&self, ip: &str) -> Result<IpBan> {
2755 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
2757 "SELECT * FROM \"xbans\" WHERE \"ip\" = ?"
2758 } else {
2759 "SELECT * FROM \"xbans\" WHERE \"ip\" = $1"
2760 };
2761
2762 let c = &self.base.db.client;
2763 let res = match sqlquery(&query).bind::<&str>(&ip).fetch_one(c).await {
2764 Ok(p) => self.base.textify_row(p).0,
2765 Err(_) => return Err(DatabaseError::NotFound),
2766 };
2767
2768 let ban = IpBan {
2770 id: from_row!(res->id()),
2771 ip: from_row!(res->ip()),
2772 reason: from_row!(res->reason()),
2773 moderator: match self.get_profile_by_id(res.get("moderator").unwrap()).await {
2774 Ok(ua) => ua,
2775 Err(e) => return Err(e),
2776 },
2777 timestamp: from_row!(res->timestamp(u128); 0),
2778 };
2779
2780 Ok(ban)
2782 }
2783
2784 pub async fn get_ipbans(&self, user: Box<Profile>) -> Result<Vec<IpBan>> {
2789 let group = match self.get_group_by_id(user.group).await {
2791 Ok(g) => g,
2792 Err(_) => return Err(DatabaseError::Other),
2793 };
2794
2795 if !group.permissions.check(FinePermission::BAN_IP) {
2796 return Err(DatabaseError::NotAllowed);
2797 }
2798
2799 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
2801 "SELECT * FROM \"xbans\" ORDER BY \"timestamp\" DESC"
2802 } else {
2803 "SELECT * FROM \"xbans\" ORDER BY \"timestamp\" DESC"
2804 };
2805
2806 let c = &self.base.db.client;
2807 let res = match sqlquery(&query).fetch_all(c).await {
2808 Ok(p) => {
2809 let mut out: Vec<IpBan> = Vec::new();
2810
2811 for row in p {
2812 let res = self.base.textify_row(row).0;
2813 out.push(IpBan {
2814 id: from_row!(res->id()),
2815 ip: from_row!(res->ip()),
2816 reason: from_row!(res->reason()),
2817 moderator: match self.get_profile_by_id(res.get("moderator").unwrap()).await
2818 {
2819 Ok(ua) => ua,
2820 Err(_) => continue,
2821 },
2822 timestamp: from_row!(res->timestamp(u128); 0),
2823 });
2824 }
2825
2826 out
2827 }
2828 Err(_) => return Err(DatabaseError::NotFound),
2829 };
2830
2831 Ok(res)
2833 }
2834
2835 pub async fn create_ipban(&self, props: IpBanCreate, user: Box<Profile>) -> Result<()> {
2842 let group = match self.get_group_by_id(user.group).await {
2844 Ok(g) => g,
2845 Err(_) => return Err(DatabaseError::Other),
2846 };
2847
2848 if !group.permissions.check(FinePermission::BAN_IP) {
2849 return Err(DatabaseError::NotAllowed);
2850 } else {
2851 let actor_id = user.id.clone();
2852 if let Err(e) = self
2853 .create_notification(
2854 NotificationCreate {
2855 title: format!("[{actor_id}](/+u/{actor_id})"),
2856 content: format!("Banned an IP: {}", props.ip),
2857 address: format!("/+u/{actor_id}"),
2858 recipient: "*(audit)".to_string(), },
2860 None,
2861 )
2862 .await
2863 {
2864 return Err(e);
2865 }
2866 }
2867
2868 if self.get_ipban_by_ip(&props.ip).await.is_ok() {
2870 return Err(DatabaseError::MustBeUnique);
2871 }
2872
2873 let ban = IpBan {
2875 id: utility::random_id(),
2876 ip: props.ip,
2877 reason: props.reason,
2878 moderator: user,
2879 timestamp: utility::unix_epoch_timestamp(),
2880 };
2881
2882 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
2884 "INSERT INTO \"xbans\" VALUES (?, ?, ?, ?, ?)"
2885 } else {
2886 "INSERT INTO \"xbans\" VALUES ($1, $2, $3, $4, $5)"
2887 };
2888
2889 let c = &self.base.db.client;
2890 match sqlquery(&query)
2891 .bind::<&str>(&ban.id)
2892 .bind::<&str>(&ban.ip)
2893 .bind::<&str>(&ban.reason)
2894 .bind::<&str>(&ban.moderator.id)
2895 .bind::<&str>(&ban.timestamp.to_string())
2896 .execute(c)
2897 .await
2898 {
2899 Ok(_) => return Ok(()),
2900 Err(_) => return Err(DatabaseError::Other),
2901 };
2902 }
2903
2904 pub async fn delete_ipban(&self, id: &str, user: Box<Profile>) -> Result<()> {
2910 let ipban = match self.get_ipban(id).await {
2912 Ok(n) => n,
2913 Err(e) => return Err(e),
2914 };
2915
2916 if user.id != ipban.moderator.id {
2918 let group = match self.get_group_by_id(user.group).await {
2920 Ok(g) => g,
2921 Err(_) => return Err(DatabaseError::Other),
2922 };
2923
2924 if !group.permissions.check(FinePermission::UNBAN_IP) {
2925 return Err(DatabaseError::NotAllowed);
2926 } else {
2927 let actor_id = user.id.clone();
2928 if let Err(e) = self
2929 .create_notification(
2930 NotificationCreate {
2931 title: format!("[{actor_id}](/+u/{actor_id})"),
2932 content: format!("Unbanned an IP: {}", ipban.ip),
2933 address: format!("/+u/{actor_id}"),
2934 recipient: "*(audit)".to_string(), },
2936 None,
2937 )
2938 .await
2939 {
2940 return Err(e);
2941 }
2942 }
2943 }
2944
2945 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
2947 "DELETE FROM \"xbans\" WHERE \"id\" = ?"
2948 } else {
2949 "DELETE FROM \"xbans\" WHERE \"id\" = $1"
2950 };
2951
2952 let c = &self.base.db.client;
2953 match sqlquery(&query).bind::<&str>(&id).execute(c).await {
2954 Ok(_) => {
2955 self.base
2957 .cache
2958 .remove(format!("rbeam.auth.ipban:{}", id))
2959 .await;
2960
2961 return Ok(());
2963 }
2964 Err(_) => return Err(DatabaseError::Other),
2965 };
2966 }
2967
2968 pub async fn get_user_relationship(
2976 &self,
2977 user: &str,
2978 other: &str,
2979 ) -> (RelationshipStatus, String, String) {
2980 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
2982 "SELECT * FROM \"xrelationships\" WHERE (\"one\" = ? AND \"two\" = ?) OR (\"one\" = ? AND \"two\" = ?)"
2983 } else {
2984 "SELECT * FROM \"xrelationships\" WHERE (\"one\" = $1 AND \"two\" = $2) OR (\"one\" = $3 AND \"two\" = $4)"
2985 };
2986
2987 let c = &self.base.db.client;
2988 let res = match sqlquery(&query)
2989 .bind::<&str>(&user)
2990 .bind::<&str>(&other)
2991 .bind::<&str>(&other)
2992 .bind::<&str>(&user)
2993 .fetch_one(c)
2994 .await
2995 {
2996 Ok(p) => self.base.textify_row(p).0,
2997 Err(_) => {
2998 return (
2999 RelationshipStatus::default(),
3000 user.to_string(),
3001 other.to_string(),
3002 )
3003 }
3004 };
3005
3006 (
3008 serde_json::from_str(&res.get("status").unwrap()).unwrap(),
3009 res.get("one").unwrap().to_string(),
3010 res.get("two").unwrap().to_string(),
3011 )
3012 }
3013
3014 pub async fn set_user_relationship_status(
3022 &self,
3023 one: &str,
3024 two: &str,
3025 status: RelationshipStatus,
3026 disable_notifications: bool,
3027 ) -> Result<()> {
3028 let mut relationship = self.get_user_relationship(one, two).await;
3030
3031 if relationship.0 == status {
3032 return Ok(());
3033 }
3034
3035 let mut uone = match self.get_profile(&relationship.1).await {
3036 Ok(ua) => ua,
3037 Err(e) => return Err(e),
3038 };
3039
3040 let mut utwo = match self.get_profile(&relationship.2).await {
3041 Ok(ua) => ua,
3042 Err(e) => return Err(e),
3043 };
3044
3045 match status {
3047 RelationshipStatus::Blocked => {
3048 if relationship.0 != RelationshipStatus::Unknown && uone.id != one {
3050 let query =
3052 if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
3053 "DELETE FROM \"xrelationships\" WHERE \"one\" = ? AND \"two\" = ?"
3054 } else {
3055 "DELETE FROM \"xrelationships\" WHERE \"one\" = ? AND \"two\" = ?"
3056 };
3057
3058 let c = &self.base.db.client;
3059 if let Err(_) = sqlquery(&query)
3060 .bind::<&str>(&uone.id)
3061 .bind::<&str>(&utwo.id)
3062 .execute(c)
3063 .await
3064 {
3065 return Err(DatabaseError::Other);
3066 };
3067
3068 relationship.0 = RelationshipStatus::Unknown; uone.id = one.to_string();
3070 utwo.id = two.to_string();
3071 }
3072
3073 if relationship.0 != RelationshipStatus::Unknown {
3075 if relationship.0 == RelationshipStatus::Friends {
3076 self.base
3078 .cache
3079 .decr(format!("rbeam.app.friends_count:{}", uone.id))
3080 .await;
3081
3082 self.base
3083 .cache
3084 .decr(format!("rbeam.app.friends_count:{}", utwo.id))
3085 .await;
3086 }
3087
3088 let query = if (self.base.db.r#type == "sqlite")
3090 | (self.base.db.r#type == "mysql")
3091 {
3092 "UPDATE \"xrelationships\" SET \"status\" = ? WHERE \"one\" = ? AND \"two\" = ?"
3093 } else {
3094 "UPDATE \"xrelationships\" SET (\"status\") = (?) WHERE \"one\" = ? AND \"two\" = ?"
3095 };
3096
3097 let c = &self.base.db.client;
3098 if let Err(_) = sqlquery(&query)
3099 .bind::<&str>(&serde_json::to_string(&status).unwrap())
3100 .bind::<&str>(&uone.id)
3101 .bind::<&str>(&utwo.id)
3102 .execute(c)
3103 .await
3104 {
3105 return Err(DatabaseError::Other);
3106 };
3107 } else {
3108 let query =
3110 if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
3111 "INSERT INTO \"xrelationships\" VALUES (?, ?, ?, ?)"
3112 } else {
3113 "INSERT INTO \"xrelationships\" VALUES ($1, $2, $3, $4)"
3114 };
3115
3116 let c = &self.base.db.client;
3117 if let Err(_) = sqlquery(&query)
3118 .bind::<&str>(&uone.id)
3119 .bind::<&str>(&utwo.id)
3120 .bind::<&str>(&serde_json::to_string(&status).unwrap())
3121 .bind::<&str>(&rainbeam_shared::unix_epoch_timestamp().to_string())
3122 .execute(c)
3123 .await
3124 {
3125 return Err(DatabaseError::Other);
3126 };
3127 }
3128 }
3129 RelationshipStatus::Pending => {
3130 if utwo.metadata.is_true("sparkler:limited_friend_requests") {
3132 if let Err(_) = self.get_follow(&utwo.id, &uone.id).await {
3134 return Err(DatabaseError::NotAllowed);
3135 }
3136 }
3137
3138 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql")
3140 {
3141 "INSERT INTO \"xrelationships\" VALUES (?, ?, ?, ?)"
3142 } else {
3143 "INSERT INTO \"xrelationships\" VALUES ($1, $2, $3, $4)"
3144 };
3145
3146 let c = &self.base.db.client;
3147 if let Err(_) = sqlquery(&query)
3148 .bind::<&str>(&uone.id)
3149 .bind::<&str>(&utwo.id)
3150 .bind::<&str>(&serde_json::to_string(&status).unwrap())
3151 .bind::<&str>(&rainbeam_shared::unix_epoch_timestamp().to_string())
3152 .execute(c)
3153 .await
3154 {
3155 return Err(DatabaseError::Other);
3156 };
3157
3158 if !disable_notifications {
3160 if let Err(_) = self
3161 .create_notification(
3162 NotificationCreate {
3163 title: format!(
3164 "[@{}](/+u/{}) has sent you a friend request!",
3165 uone.username, uone.id
3166 ),
3167 content: format!("{} wants to be your friend.", uone.username),
3168 address: format!("/@{}/relationship/friend_accept", uone.id),
3169 recipient: utwo.id,
3170 },
3171 None,
3172 )
3173 .await
3174 {
3175 return Err(DatabaseError::Other);
3176 };
3177 };
3178 }
3179 RelationshipStatus::Friends => {
3180 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql")
3182 {
3183 "UPDATE \"xrelationships\" SET \"status\" = ? WHERE \"one\" = ? AND \"two\" = ?"
3184 } else {
3185 "UPDATE \"xrelationships\" SET (\"status\") = (?) WHERE \"one\" = ? AND \"two\" = ?"
3186 };
3187
3188 let c = &self.base.db.client;
3189 if let Err(_) = sqlquery(&query)
3190 .bind::<&str>(&serde_json::to_string(&status).unwrap())
3191 .bind::<&str>(&uone.id)
3192 .bind::<&str>(&utwo.id)
3193 .execute(c)
3194 .await
3195 {
3196 return Err(DatabaseError::Other);
3197 };
3198
3199 self.base
3200 .cache
3201 .incr(format!("rbeam.app.friends_count:{}", uone.id))
3202 .await;
3203
3204 self.base
3205 .cache
3206 .incr(format!("rbeam.app.friends_count:{}", utwo.id))
3207 .await;
3208
3209 if !disable_notifications {
3211 if let Err(_) = self
3212 .create_notification(
3213 NotificationCreate {
3214 title: "Your friend request has been accepted!".to_string(),
3215 content: format!(
3216 "[@{}](/@{}) has accepted your friend request.",
3217 utwo.username, utwo.username
3218 ),
3219 address: String::new(),
3220 recipient: uone.id,
3221 },
3222 None,
3223 )
3224 .await
3225 {
3226 return Err(DatabaseError::Other);
3227 };
3228 };
3229 }
3230 RelationshipStatus::Unknown => {
3231 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql")
3233 {
3234 "DELETE FROM \"xrelationships\" WHERE \"one\" = ? AND \"two\" = ?"
3235 } else {
3236 "DELETE FROM \"xrelationships\" WHERE \"one\" = ? AND \"two\" = ?"
3237 };
3238
3239 let c = &self.base.db.client;
3240 if let Err(_) = sqlquery(&query)
3241 .bind::<&str>(&uone.id)
3242 .bind::<&str>(&utwo.id)
3243 .execute(c)
3244 .await
3245 {
3246 return Err(DatabaseError::Other);
3247 };
3248
3249 if relationship.0 == RelationshipStatus::Friends {
3250 self.base
3252 .cache
3253 .decr(format!("rbeam.app.friends_count:{}", uone.id))
3254 .await;
3255
3256 self.base
3257 .cache
3258 .decr(format!("rbeam.app.friends_count:{}", utwo.id))
3259 .await;
3260 }
3261 }
3262 }
3263
3264 Ok(())
3266 }
3267
3268 pub async fn get_user_relationships(
3273 &self,
3274 user: &str,
3275 ) -> Result<Vec<(Box<Profile>, RelationshipStatus)>> {
3276 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
3278 "SELECT * FROM \"xrelationships\" WHERE \"one\" = ?"
3279 } else {
3280 "SELECT * FROM \"xrelationships\" WHERE \"one\" = $1"
3281 };
3282
3283 let c = &self.base.db.client;
3284 match sqlquery(&query).bind::<&str>(&user).fetch_all(c).await {
3285 Ok(p) => {
3286 let mut out = Vec::new();
3287
3288 for row in p {
3289 let res = self.base.textify_row(row).0;
3290
3291 let profile = match self.get_profile(res.get("two").unwrap()).await {
3293 Ok(c) => c,
3294 Err(e) => return Err(e),
3295 };
3296
3297 out.push((
3299 profile,
3300 serde_json::from_str(&res.get("status").unwrap()).unwrap(),
3301 ));
3302 }
3303
3304 Ok(out)
3305 }
3306 Err(_) => return Err(DatabaseError::Other),
3307 }
3308 }
3309
3310 pub async fn get_user_relationships_of_status(
3316 &self,
3317 user: &str,
3318 status: RelationshipStatus,
3319 ) -> Result<Vec<(Box<Profile>, RelationshipStatus)>> {
3320 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
3322 "SELECT * FROM \"xrelationships\" WHERE \"one\" = ? AND \"status\" = ?"
3323 } else {
3324 "SELECT * FROM \"xrelationships\" WHERE \"one\" = $1 AND \"status\" = $2"
3325 };
3326
3327 let c = &self.base.db.client;
3328 match sqlquery(&query)
3329 .bind::<&str>(&user)
3330 .bind::<&str>(&serde_json::to_string(&status).unwrap())
3331 .fetch_all(c)
3332 .await
3333 {
3334 Ok(p) => {
3335 let mut out = Vec::new();
3336
3337 for row in p {
3338 let res = self.base.textify_row(row).0;
3339
3340 let profile = match self.get_profile(res.get("two").unwrap()).await {
3342 Ok(c) => c,
3343 Err(_) => continue,
3344 };
3345
3346 out.push((
3348 profile,
3349 serde_json::from_str(&res.get("status").unwrap()).unwrap(),
3350 ));
3351 }
3352
3353 Ok(out)
3354 }
3355 Err(_) => return Err(DatabaseError::Other),
3356 }
3357 }
3358
3359 pub async fn get_user_participating_relationships_of_status(
3365 &self,
3366 user: &str,
3367 status: RelationshipStatus,
3368 ) -> Result<Vec<(Box<Profile>, Box<Profile>)>> {
3369 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
3371 "SELECT * FROM \"xrelationships\" WHERE (\"one\" = ? OR \"two\" = ?) AND \"status\" = ?"
3372 } else {
3373 "SELECT * FROM \"xrelationships\" WHERE (\"one\" = $1 OR \"two\" = $2) AND \"status\" = $3"
3374 };
3375
3376 let c = &self.base.db.client;
3377 match sqlquery(&query)
3378 .bind::<&str>(&user)
3379 .bind::<&str>(&user)
3380 .bind::<&str>(&serde_json::to_string(&status).unwrap())
3381 .fetch_all(c)
3382 .await
3383 {
3384 Ok(p) => {
3385 let mut out = Vec::new();
3386
3387 for row in p {
3388 let res = self.base.textify_row(row).0;
3389
3390 let profile = match self.get_profile(res.get("one").unwrap()).await {
3392 Ok(c) => c,
3393 Err(_) => continue,
3394 };
3395
3396 let profile_2 = match self.get_profile(res.get("two").unwrap()).await {
3397 Ok(c) => c,
3398 Err(_) => continue,
3399 };
3400
3401 out.push((profile, profile_2));
3403 }
3404
3405 Ok(out)
3406 }
3407 Err(_) => return Err(DatabaseError::Other),
3408 }
3409 }
3410
3411 pub async fn get_user_participating_relationships_of_status_paginated(
3418 &self,
3419 user: &str,
3420 status: RelationshipStatus,
3421 page: i32,
3422 ) -> Result<Vec<(Box<Profile>, Box<Profile>)>> {
3423 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
3425 format!("SELECT * FROM \"xrelationships\" WHERE (\"one\" = ? OR \"two\" = ?) AND \"status\" = ? ORDER BY \"timestamp\" DESC LIMIT 12 OFFSET {}", page * 12)
3426 } else {
3427 format!("SELECT * FROM \"xrelationships\" WHERE (\"one\" = $1 OR \"two\" = $2) AND \"status\" = $3 ORDER BY \"timestamp\" DESC LIMIT 12 OFFSET {}", page * 12)
3428 };
3429
3430 let c = &self.base.db.client;
3431 match sqlquery(&query)
3432 .bind::<&str>(&user)
3433 .bind::<&str>(&user)
3434 .bind::<&str>(&serde_json::to_string(&status).unwrap())
3435 .fetch_all(c)
3436 .await
3437 {
3438 Ok(p) => {
3439 let mut out = Vec::new();
3440
3441 for row in p {
3442 let res = self.base.textify_row(row).0;
3443
3444 let profile = match self.get_profile(res.get("one").unwrap()).await {
3446 Ok(c) => c,
3447 Err(e) => return Err(e),
3448 };
3449
3450 let profile_2 = match self.get_profile(res.get("two").unwrap()).await {
3451 Ok(c) => c,
3452 Err(e) => return Err(e),
3453 };
3454
3455 out.push((profile, profile_2));
3457 }
3458
3459 Ok(out)
3460 }
3461 Err(_) => return Err(DatabaseError::Other),
3462 }
3463 }
3464
3465 pub async fn get_friendship_count_by_user(&self, id: &str) -> usize {
3470 if let Some(count) = self
3472 .base
3473 .cache
3474 .get(format!("rbeam.app.friends_count:{}", id))
3475 .await
3476 {
3477 return count.parse::<usize>().unwrap_or(0);
3478 };
3479
3480 let count = self
3482 .get_user_participating_relationships_of_status(id, RelationshipStatus::Friends)
3483 .await
3484 .unwrap_or(Vec::new())
3485 .len();
3486
3487 self.base
3488 .cache
3489 .set(format!("rbeam.app.friends_count:{}", id), count.to_string())
3490 .await;
3491
3492 count
3493 }
3494
3495 pub async fn get_ipblock(&self, id: &str) -> Result<IpBlock> {
3503 match self
3505 .base
3506 .cache
3507 .get(format!("rbeam.auth.ipblock:{}", id))
3508 .await
3509 {
3510 Some(c) => return Ok(serde_json::from_str::<IpBlock>(c.as_str()).unwrap()),
3511 None => (),
3512 };
3513
3514 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
3516 "SELECT * FROM \"xipblocks\" WHERE \"id\" = ?"
3517 } else {
3518 "SELECT * FROM \"xipblocks\" WHERE \"id\" = $1"
3519 };
3520
3521 let c = &self.base.db.client;
3522 let res = match sqlquery(&query).bind::<&str>(&id).fetch_one(c).await {
3523 Ok(p) => self.base.textify_row(p).0,
3524 Err(_) => return Err(DatabaseError::NotFound),
3525 };
3526
3527 let block = IpBlock {
3529 id: from_row!(res->id()),
3530 ip: from_row!(res->ip()),
3531 user: from_row!(res->user()),
3532 context: from_row!(res->context()),
3533 timestamp: from_row!(res->timestamp(u128); 0),
3534 };
3535
3536 self.base
3538 .cache
3539 .set(
3540 format!("rbeam.auth.ipblock:{}", id),
3541 serde_json::to_string::<IpBlock>(&block).unwrap(),
3542 )
3543 .await;
3544
3545 Ok(block)
3547 }
3548
3549 pub async fn get_ipblock_by_ip(&self, ip: &str, user: &str) -> Result<IpBlock> {
3555 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
3557 "SELECT * FROM \"xipblocks\" WHERE \"ip\" = ? AND \"user\" = ?"
3558 } else {
3559 "SELECT * FROM \"xipblocks\" WHERE \"ip\" = $1 AND \"user\" = $2"
3560 };
3561
3562 let c = &self.base.db.client;
3563 let res = match sqlquery(&query)
3564 .bind::<&str>(&ip)
3565 .bind::<&str>(&user)
3566 .fetch_one(c)
3567 .await
3568 {
3569 Ok(p) => self.base.textify_row(p).0,
3570 Err(_) => return Err(DatabaseError::NotFound),
3571 };
3572
3573 let block = IpBlock {
3575 id: from_row!(res->id()),
3576 ip: from_row!(res->ip()),
3577 user: from_row!(res->user()),
3578 context: from_row!(res->context()),
3579 timestamp: from_row!(res->timestamp(u128); 0),
3580 };
3581
3582 Ok(block)
3584 }
3585
3586 pub async fn get_ipblocks(&self, query_user: &str) -> Result<Vec<IpBlock>> {
3591 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
3593 "SELECT * FROM \"xipblocks\" WHERE \"user\" = ? ORDER BY \"timestamp\" DESC"
3594 } else {
3595 "SELECT * FROM \"xipblocks\" WHERE \"user\" = $1 ORDER BY \"timestamp\" DESC"
3596 };
3597
3598 let c = &self.base.db.client;
3599 let res = match sqlquery(&query)
3600 .bind::<&str>(&query_user)
3601 .fetch_all(c)
3602 .await
3603 {
3604 Ok(p) => {
3605 let mut out: Vec<IpBlock> = Vec::new();
3606
3607 for row in p {
3608 let res = self.base.textify_row(row).0;
3609 out.push(IpBlock {
3610 id: from_row!(res->id()),
3611 ip: from_row!(res->ip()),
3612 user: from_row!(res->user()),
3613 context: from_row!(res->context()),
3614 timestamp: from_row!(res->timestamp(u128); 0),
3615 });
3616 }
3617
3618 out
3619 }
3620 Err(_) => return Err(DatabaseError::NotFound),
3621 };
3622
3623 Ok(res)
3625 }
3626
3627 pub async fn create_ipblock(&self, props: IpBlockCreate, user: Box<Profile>) -> Result<()> {
3634 if self.get_ipblock_by_ip(&props.ip, &user.id).await.is_ok() {
3636 return Err(DatabaseError::MustBeUnique);
3637 }
3638
3639 let block = IpBlock {
3641 id: utility::random_id(),
3642 ip: props.ip,
3643 user: user.id,
3644 context: props.context,
3645 timestamp: utility::unix_epoch_timestamp(),
3646 };
3647
3648 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
3650 "INSERT INTO \"xipblocks\" VALUES (?, ?, ?, ?, ?)"
3651 } else {
3652 "INSERT INTO \"xipblocks\" VALUES ($1, $2, $3, $4, $5)"
3653 };
3654
3655 let c = &self.base.db.client;
3656 match sqlquery(&query)
3657 .bind::<&str>(&block.id)
3658 .bind::<&str>(&block.ip)
3659 .bind::<&str>(&block.user)
3660 .bind::<&str>(&block.context)
3661 .bind::<&str>(&block.timestamp.to_string())
3662 .execute(c)
3663 .await
3664 {
3665 Ok(_) => return Ok(()),
3666 Err(_) => return Err(DatabaseError::Other),
3667 };
3668 }
3669
3670 pub async fn delete_ipblock(&self, id: &str, user: Box<Profile>) -> Result<()> {
3676 let block = match self.get_ipblock(id).await {
3678 Ok(n) => n,
3679 Err(e) => return Err(e),
3680 };
3681
3682 if user.id != block.user {
3684 let group = match self.get_group_by_id(user.group).await {
3686 Ok(g) => g,
3687 Err(_) => return Err(DatabaseError::Other),
3688 };
3689
3690 if !group.permissions.check(FinePermission::UNBAN_IP) {
3691 return Err(DatabaseError::NotAllowed);
3692 } else {
3693 let actor_id = user.id.clone();
3694 if let Err(e) = self
3695 .create_notification(
3696 NotificationCreate {
3697 title: format!("[{actor_id}](/+u/{actor_id})"),
3698 content: format!("Unblocked an IP: {}", block.ip),
3699 address: format!("/+u/{actor_id}"),
3700 recipient: "*(audit)".to_string(), },
3702 None,
3703 )
3704 .await
3705 {
3706 return Err(e);
3707 }
3708 }
3709 }
3710
3711 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
3713 "DELETE FROM \"xipblocks\" WHERE \"id\" = ?"
3714 } else {
3715 "DELETE FROM \"xipblocks\" WHERE \"id\" = $1"
3716 };
3717
3718 let c = &self.base.db.client;
3719 match sqlquery(&query).bind::<&str>(&id).execute(c).await {
3720 Ok(_) => {
3721 self.base
3723 .cache
3724 .remove(format!("rbeam.auth.ipblock:{}", id))
3725 .await;
3726
3727 return Ok(());
3729 }
3730 Err(_) => return Err(DatabaseError::Other),
3731 };
3732 }
3733
3734 pub async fn gimme_label(&self, res: BTreeMap<String, String>) -> Result<UserLabel> {
3738 Ok(UserLabel {
3739 id: from_row!(res->id(i64); 0),
3740 name: from_row!(res->name()),
3741 timestamp: from_row!(res->timestamp(u128); 0),
3742 creator: from_row!(res->creator()),
3743 })
3744 }
3745
3746 pub async fn get_label(&self, id: i64) -> Result<UserLabel> {
3751 match self
3753 .base
3754 .cache
3755 .get(format!("rbeam.auth.label:{}", id))
3756 .await
3757 {
3758 Some(c) => match serde_json::from_str::<UserLabel>(c.as_str()) {
3759 Ok(c) => return Ok(c),
3760 Err(_) => {
3761 self.base
3762 .cache
3763 .remove(format!("rbeam.auth.label:{}", id))
3764 .await;
3765 }
3766 },
3767 None => (),
3768 };
3769
3770 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
3772 "SELECT * FROM \"xlabels\" WHERE \"id\" = ?"
3773 } else {
3774 "SELECT * FROM \"xlabels\" WHERE \"id\" = $1"
3775 };
3776
3777 let c = &self.base.db.client;
3778 let res = match sqlquery(&query).bind::<i64>(id).fetch_one(c).await {
3779 Ok(p) => self.base.textify_row(p).0,
3780 Err(_) => return Err(DatabaseError::NotFound),
3781 };
3782
3783 let label = match self.gimme_label(res).await {
3785 Ok(l) => l,
3786 Err(e) => return Err(e),
3787 };
3788
3789 self.base
3791 .cache
3792 .set(
3793 format!("rbeam.auth.label:{}", id),
3794 serde_json::to_string::<UserLabel>(&label).unwrap(),
3795 )
3796 .await;
3797
3798 Ok(label)
3800 }
3801
3802 pub async fn create_label(&self, name: &str, id: i64, author: &str) -> Result<UserLabel> {
3809 let author = match self.get_profile(author).await {
3811 Ok(ua) => ua,
3812 Err(e) => return Err(e),
3813 };
3814
3815 let group = match self.get_group_by_id(author.group).await {
3816 Ok(g) => g,
3817 Err(e) => return Err(e),
3818 };
3819
3820 if !group.permissions.check(FinePermission::CREATE_LABEL) {
3821 return Err(DatabaseError::NotAllowed);
3822 }
3823
3824 if name.len() < 2 {
3826 return Err(DatabaseError::Other);
3827 }
3828
3829 if name.len() > 32 {
3830 return Err(DatabaseError::Other);
3831 }
3832
3833 let label = UserLabel {
3835 id,
3837 name: name.to_string(),
3838 timestamp: utility::unix_epoch_timestamp(),
3839 creator: author.id,
3840 };
3841
3842 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
3844 "INSERT INTO \"xlabels\" VALUES (?, ?, ?, ?)"
3845 } else {
3846 "INSERT INTO \"xlabels\" VALUES ($1, $2, $3, $4)"
3847 };
3848
3849 let c = &self.base.db.client;
3850 match sqlquery(&query)
3851 .bind::<i64>(label.id)
3852 .bind::<&str>(&label.name)
3853 .bind::<&str>(&label.timestamp.to_string())
3854 .bind::<&str>(&label.creator)
3855 .execute(c)
3856 .await
3857 {
3858 Ok(_) => Ok(label),
3859 Err(_) => Err(DatabaseError::Other),
3860 }
3861 }
3862
3863 pub async fn delete_label(&self, id: i64, author: Box<Profile>) -> Result<()> {
3869 let group = match self.get_group_by_id(author.group).await {
3871 Ok(g) => g,
3872 Err(e) => return Err(e),
3873 };
3874
3875 if !group.permissions.check(FinePermission::MANAGE_LABELS) {
3876 return Err(DatabaseError::NotAllowed);
3877 }
3878
3879 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
3881 "DELETE FROM \"xlabels\" WHERE \"id\" = ?"
3882 } else {
3883 "DELETE FROM \"xlabels\" WHERE \"id\" = $1"
3884 };
3885
3886 let c = &self.base.db.client;
3887 let res = match sqlquery(&query).bind::<i64>(id).execute(c).await {
3888 Ok(_) => Ok(()),
3889 Err(_) => Err(DatabaseError::Other),
3890 };
3891
3892 self.base
3893 .cache
3894 .remove(format!("rbeam.auth.label:{}", id))
3895 .await;
3896
3897 res
3898 }
3899
3900 pub async fn gimme_transaction(
3904 &self,
3905 row: BTreeMap<String, String>,
3906 ) -> Result<(Transaction, Option<Item>)> {
3907 let item = row.get("item").unwrap();
3908
3909 Ok((
3910 Transaction {
3911 id: from_row!(row->id()),
3912 amount: row.get("amount").unwrap().parse::<i32>().unwrap_or(0),
3913 item: item.clone(),
3914 timestamp: row.get("timestamp").unwrap().parse::<u128>().unwrap(),
3915 customer: from_row!(row->customer()),
3916 merchant: from_row!(row->merchant()),
3917 },
3918 match self.get_item(item).await {
3919 Ok(i) => Some(i),
3920 Err(_) => None,
3921 },
3922 ))
3923 }
3924
3925 pub async fn get_transaction(&self, id: &str) -> Result<(Transaction, Option<Item>)> {
3931 match self
3933 .base
3934 .cache
3935 .get(format!("rbeam.auth.econ.transaction:{}", id))
3936 .await
3937 {
3938 Some(c) => match serde_json::from_str::<BTreeMap<String, String>>(c.as_str()) {
3939 Ok(c) => {
3940 return Ok(match self.gimme_transaction(c).await {
3941 Ok(t) => t,
3942 Err(e) => return Err(e),
3943 })
3944 }
3945 Err(_) => {
3946 self.base
3947 .cache
3948 .remove(format!("rbeam.auth.econ.transaction:{}", id))
3949 .await;
3950 }
3951 },
3952 None => (),
3953 };
3954
3955 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
3957 "SELECT * FROM \"xugc_transactions\" WHERE \"id\" = ?"
3958 } else {
3959 "SELECT * FROM \"xugc_transactions\" WHERE \"id\" = $1"
3960 };
3961
3962 let c = &self.base.db.client;
3963 let res = match sqlquery(&query).bind::<&str>(&id).fetch_one(c).await {
3964 Ok(p) => self.base.textify_row(p).0,
3965 Err(_) => return Err(DatabaseError::NotFound),
3966 };
3967
3968 let transaction = match self.gimme_transaction(res).await {
3970 Ok(t) => t,
3971 Err(e) => return Err(e),
3972 };
3973
3974 self.base
3976 .cache
3977 .set(
3978 format!("rbeam.auth.econ.transaction:{}", id),
3979 serde_json::to_string::<Transaction>(&transaction.0).unwrap(),
3980 )
3981 .await;
3982
3983 Ok(transaction)
3985 }
3986
3987 pub async fn get_transaction_by_customer_item(
3993 &self,
3994 customer: &str,
3995 item: &str,
3996 ) -> Result<(Transaction, Option<Item>)> {
3997 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
3999 "SELECT * FROM \"xugc_transactions\" WHERE \"customer\" = ? AND \"item\" = ?"
4000 } else {
4001 "SELECT * FROM \"xugc_transactions\" WHERE \"customer\" = $1 AND \"item\" = $2"
4002 };
4003
4004 let c = &self.base.db.client;
4005 let res = match sqlquery(&query)
4006 .bind::<&str>(&customer)
4007 .bind::<&str>(&item)
4008 .fetch_one(c)
4009 .await
4010 {
4011 Ok(p) => self.base.textify_row(p).0,
4012 Err(_) => return Err(DatabaseError::NotFound),
4013 };
4014
4015 let transaction = match self.gimme_transaction(res).await {
4017 Ok(t) => t,
4018 Err(e) => return Err(e),
4019 };
4020
4021 Ok(transaction)
4023 }
4024
4025 pub async fn get_transactions_by_customer_paginated(
4034 &self,
4035 user: &str,
4036 page: i32,
4037 ) -> Result<Vec<((Transaction, Option<Item>), Box<Profile>, Box<Profile>)>> {
4038 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
4040 format!("SELECT * FROM \"xugc_transactions\" WHERE \"customer\" = ? ORDER BY \"timestamp\" DESC LIMIT 12 OFFSET {}", page * 12)
4041 } else {
4042 format!("SELECT * FROM \"xugc_transactions\" WHERE \"customer\" = $1 ORDER BY \"timestamp\" DESC LIMIT 12 OFFSET {}", page * 12)
4043 };
4044
4045 let c = &self.base.db.client;
4046 let res = match sqlquery(&query)
4047 .bind::<&str>(&user)
4048 .bind::<&str>(&user)
4049 .fetch_all(c)
4050 .await
4051 {
4052 Ok(p) => {
4053 let mut out = Vec::new();
4054
4055 for row in p {
4056 let res = self.base.textify_row(row).0;
4057 let transaction = match self.gimme_transaction(res).await {
4058 Ok(t) => t,
4059 Err(e) => return Err(e),
4060 };
4061
4062 let customer = transaction.0.customer.clone();
4063 let merchant = transaction.0.merchant.clone();
4064
4065 out.push((
4066 transaction.clone(),
4067 match self.get_profile(&customer).await {
4068 Ok(ua) => ua,
4069 Err(_) => continue,
4070 },
4071 match self.get_profile(&merchant).await {
4072 Ok(ua) => ua,
4073 Err(_) => continue,
4074 },
4075 ));
4076 }
4077
4078 out
4079 }
4080 Err(_) => return Err(DatabaseError::Other),
4081 };
4082
4083 Ok(res)
4085 }
4086
4087 pub async fn get_participating_transactions_paginated(
4096 &self,
4097 user: &str,
4098 page: i32,
4099 ) -> Result<Vec<((Transaction, Option<Item>), Box<Profile>, Box<Profile>)>> {
4100 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
4102 format!("SELECT * FROM \"xugc_transactions\" WHERE \"customer\" = ? OR \"merchant\" = ? ORDER BY \"timestamp\" DESC LIMIT 12 OFFSET {}", page * 12)
4103 } else {
4104 format!("SELECT * FROM \"xugc_transactions\" WHERE \"customer\" = $1 OR \"merchant\" = $2 ORDER BY \"timestamp\" DESC LIMIT 12 OFFSET {}", page * 12)
4105 };
4106
4107 let c = &self.base.db.client;
4108 let res = match sqlquery(&query)
4109 .bind::<&str>(&user)
4110 .bind::<&str>(&user)
4111 .fetch_all(c)
4112 .await
4113 {
4114 Ok(p) => {
4115 let mut out = Vec::new();
4116
4117 for row in p {
4118 let res = self.base.textify_row(row).0;
4119 let transaction = match self.gimme_transaction(res).await {
4120 Ok(t) => t,
4121 Err(e) => return Err(e),
4122 };
4123
4124 let customer = transaction.0.customer.clone();
4125 let merchant = transaction.0.merchant.clone();
4126
4127 out.push((
4128 transaction.clone(),
4129 match self.get_profile(&customer).await {
4130 Ok(ua) => ua,
4131 Err(_) => continue,
4132 },
4133 match self.get_profile(&merchant).await {
4134 Ok(ua) => ua,
4135 Err(_) => continue,
4136 },
4137 ));
4138 }
4139
4140 out
4141 }
4142 Err(_) => return Err(DatabaseError::Other),
4143 };
4144
4145 Ok(res)
4147 }
4148
4149 pub async fn create_transaction(
4156 &self,
4157 props: TransactionCreate,
4158 customer: &str,
4159 ) -> Result<Transaction> {
4160 let customer = match self.get_profile(customer).await {
4162 Ok(ua) => ua,
4163 Err(e) => return Err(e),
4164 };
4165
4166 let merchant = match self.get_profile(&props.merchant).await {
4167 Ok(ua) => ua,
4168 Err(e) => return Err(e),
4169 };
4170
4171 if props.amount.is_negative() {
4173 if (customer.coins + props.amount) < 0 {
4174 return Err(DatabaseError::TooExpensive);
4175 }
4176 }
4177
4178 let transaction = Transaction {
4180 id: AlmostSnowflake::new(self.config.snowflake_server_id)
4182 .to_string()
4183 .to_string(),
4184 amount: props.amount,
4185 item: props.item,
4186 timestamp: utility::unix_epoch_timestamp(),
4187 customer: customer.id.clone(),
4188 merchant: merchant.id.clone(),
4189 };
4190
4191 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
4193 "INSERT INTO \"xugc_transactions\" VALUES (?, ?, ?, ?, ?, ?)"
4194 } else {
4195 "INSERT INTO \"xugc_transactions\" VALUES ($1, $2, $3, $4, $5, $6)"
4196 };
4197
4198 let c = &self.base.db.client;
4199 match sqlquery(&query)
4200 .bind::<&str>(&transaction.id)
4201 .bind::<i32>(transaction.amount)
4202 .bind::<&str>(&transaction.item)
4203 .bind::<&str>(&transaction.timestamp.to_string())
4204 .bind::<&str>(&transaction.customer)
4205 .bind::<&str>(&transaction.merchant)
4206 .execute(c)
4207 .await
4208 {
4209 Ok(_) => {
4210 if let Err(e) = self
4212 .update_profile_coins(&customer.id, transaction.amount)
4213 .await
4214 {
4215 return Err(e);
4216 };
4217
4218 if let Err(e) = self
4219 .update_profile_coins(&merchant.id, transaction.amount.abs())
4220 .await
4221 {
4222 return Err(e);
4223 };
4224
4225 if (customer.id != merchant.id) && (merchant.id != "0") {
4227 if let Err(e) = self
4228 .create_notification(
4229 NotificationCreate {
4230 title: "Purchased data now available!".to_string(),
4231 content: "Data from an item you purchased is now available."
4232 .to_string(),
4233 address: format!(
4234 "/market/item/{}#/preview",
4235 transaction.item.clone()
4236 ),
4237 recipient: customer.id,
4238 },
4239 None,
4240 )
4241 .await
4242 {
4243 return Err(e);
4244 }
4245 }
4246
4247 return Ok(transaction);
4249 }
4250 Err(_) => return Err(DatabaseError::Other),
4251 };
4252 }
4253
4254 pub fn gimme_item(&self, row: BTreeMap<String, String>) -> Result<Item> {
4258 Ok(Item {
4259 id: from_row!(row->id()),
4260 name: from_row!(row->name()),
4261 description: from_row!(row->description()),
4262 cost: row.get("cost").unwrap().parse::<i32>().unwrap_or(0),
4263 content: from_row!(row->content()),
4264 r#type: match serde_json::from_str(row.get("type").unwrap()) {
4265 Ok(v) => v,
4266 Err(_) => return Err(DatabaseError::ValueError),
4267 },
4268 status: match serde_json::from_str(row.get("status").unwrap()) {
4269 Ok(v) => v,
4270 Err(_) => return Err(DatabaseError::ValueError),
4271 },
4272 timestamp: row.get("timestamp").unwrap().parse::<u128>().unwrap(),
4273 creator: from_row!(row->creator()),
4274 })
4275 }
4276
4277 pub async fn get_item(&self, id: &str) -> Result<Item> {
4283 if id == "0" {
4284 return Ok(Item {
4286 id: "0".to_string(),
4287 name: "System cost".to_string(),
4288 description: String::new(),
4289 cost: -1,
4290 content: String::new(),
4291 r#type: ItemType::Text,
4292 status: ItemStatus::Approved,
4293 timestamp: 0,
4294 creator: "0".to_string(),
4295 });
4296 }
4297
4298 match self
4300 .base
4301 .cache
4302 .get(format!("rbeam.auth.econ.item:{}", id))
4303 .await
4304 {
4305 Some(c) => match serde_json::from_str::<Item>(c.as_str()) {
4306 Ok(c) => return Ok(c),
4307 Err(_) => {
4308 self.base
4309 .cache
4310 .remove(format!("rbeam.auth.econ.item:{}", id))
4311 .await;
4312 }
4313 },
4314 None => (),
4315 };
4316
4317 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
4319 "SELECT * FROM \"xugc_items\" WHERE \"id\" = ?"
4320 } else {
4321 "SELECT * FROM \"xugc_items\" WHERE \"id\" = $1"
4322 };
4323
4324 let c = &self.base.db.client;
4325 let res = match sqlquery(&query).bind::<&str>(&id).fetch_one(c).await {
4326 Ok(p) => self.base.textify_row(p).0,
4327 Err(_) => return Err(DatabaseError::NotFound),
4328 };
4329
4330 let item = match self.gimme_item(res) {
4332 Ok(t) => t,
4333 Err(e) => return Err(e),
4334 };
4335
4336 self.base
4338 .cache
4339 .set(
4340 format!("rbeam.auth.econ.item:{}", id),
4341 serde_json::to_string::<Item>(&item).unwrap(),
4342 )
4343 .await;
4344
4345 Ok(item)
4347 }
4348
4349 pub async fn get_items_by_creator_paginated(
4358 &self,
4359 user: &str,
4360 page: i32,
4361 ) -> Result<Vec<(Item, Box<Profile>)>> {
4362 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
4364 format!("SELECT * FROM \"xugc_items\" WHERE \"creator\" = ? ORDER BY \"timestamp\" DESC LIMIT 12 OFFSET {}", page * 12)
4365 } else {
4366 format!("SELECT * FROM \"xugc_items\" WHERE \"creator\" = $1 ORDER BY \"timestamp\" DESC LIMIT 12 OFFSET {}", page * 12)
4367 };
4368
4369 let c = &self.base.db.client;
4370 let res = match sqlquery(&query).bind::<&str>(&user).fetch_all(c).await {
4371 Ok(p) => {
4372 let mut out = Vec::new();
4373
4374 for row in p {
4375 let res = self.base.textify_row(row).0;
4376 let item = match self.gimme_item(res) {
4377 Ok(t) => t,
4378 Err(e) => return Err(e),
4379 };
4380
4381 let creator = item.creator.clone();
4382
4383 out.push((
4384 item,
4385 match self.get_profile(&creator).await {
4386 Ok(ua) => ua,
4387 Err(_) => continue,
4388 },
4389 ));
4390 }
4391
4392 out
4393 }
4394 Err(_) => return Err(DatabaseError::Other),
4395 };
4396
4397 Ok(res)
4399 }
4400
4401 pub async fn get_items_by_creator_type_paginated(
4411 &self,
4412 user: &str,
4413 r#type: ItemType,
4414 page: i32,
4415 ) -> Result<Vec<(Item, Box<Profile>)>> {
4416 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
4418 format!("SELECT * FROM \"xugc_items\" WHERE \"creator\" = ? AND \"type\" = ? ORDER BY \"timestamp\" DESC LIMIT 12 OFFSET {}", page * 12)
4419 } else {
4420 format!("SELECT * FROM \"xugc_items\" WHERE \"creator\" = $1 AND \"type\" = $2 ORDER BY \"timestamp\" DESC LIMIT 12 OFFSET {}", page * 12)
4421 };
4422
4423 let c = &self.base.db.client;
4424 let res = match sqlquery(&query)
4425 .bind::<&str>(&user)
4426 .bind::<&str>(&serde_json::to_string(&r#type).unwrap())
4427 .fetch_all(c)
4428 .await
4429 {
4430 Ok(p) => {
4431 let mut out = Vec::new();
4432
4433 for row in p {
4434 let res = self.base.textify_row(row).0;
4435 let item = match self.gimme_item(res) {
4436 Ok(t) => t,
4437 Err(e) => return Err(e),
4438 };
4439
4440 let creator = item.creator.clone();
4441
4442 out.push((
4443 item,
4444 match self.get_profile(&creator).await {
4445 Ok(ua) => ua,
4446 Err(_) => continue,
4447 },
4448 ));
4449 }
4450
4451 out
4452 }
4453 Err(_) => return Err(DatabaseError::Other),
4454 };
4455
4456 Ok(res)
4458 }
4459
4460 pub async fn get_items_by_status_searched_paginated(
4469 &self,
4470 status: ItemStatus,
4471 page: i32,
4472 search: &str,
4473 ) -> Result<Vec<(Item, Box<Profile>)>> {
4474 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
4476 format!("SELECT * FROM \"xugc_items\" WHERE \"status\" = ? AND \"name\" LIKE ? AND \"cost\" != '-1' ORDER BY \"timestamp\" DESC LIMIT 12 OFFSET {}", page * 12)
4477 } else {
4478 format!("SELECT * FROM \"xugc_items\" WHERE \"status\" = $1 AND \"name\" LIKE $2 AND \"cost\" != '-1' ORDER BY \"timestamp\" DESC LIMIT 12 OFFSET {}", page * 12)
4479 };
4480
4481 let c = &self.base.db.client;
4482 let res = match sqlquery(&query)
4483 .bind::<&str>(&serde_json::to_string(&status).unwrap())
4484 .bind::<&str>(&format!("%{search}%"))
4485 .fetch_all(c)
4486 .await
4487 {
4488 Ok(p) => {
4489 let mut out = Vec::new();
4490
4491 for row in p {
4492 let res = self.base.textify_row(row).0;
4493 let item = match self.gimme_item(res) {
4494 Ok(t) => t,
4495 Err(e) => return Err(e),
4496 };
4497
4498 let creator = item.creator.clone();
4499
4500 out.push((
4501 item,
4502 match self.get_profile(&creator).await {
4503 Ok(ua) => ua,
4504 Err(_) => continue,
4505 },
4506 ));
4507 }
4508
4509 out
4510 }
4511 Err(_) => return Err(DatabaseError::Other),
4512 };
4513
4514 Ok(res)
4516 }
4517
4518 pub async fn get_items_by_type_paginated(
4527 &self,
4528 r#type: ItemType,
4529 page: i32,
4530 ) -> Result<Vec<(Item, Box<Profile>)>> {
4531 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
4533 format!("SELECT * FROM \"xugc_items\" WHERE \"type\" = ? ORDER BY \"timestamp\" DESC LIMIT 12 OFFSET {}", page * 12)
4534 } else {
4535 format!("SELECT * FROM \"xugc_items\" WHERE \"type\" = $2 ORDER BY \"timestamp\" DESC LIMIT 12 OFFSET {}", page * 12)
4536 };
4537
4538 let c = &self.base.db.client;
4539 let res = match sqlquery(&query)
4540 .bind::<&str>(&serde_json::to_string(&r#type).unwrap())
4541 .fetch_all(c)
4542 .await
4543 {
4544 Ok(p) => {
4545 let mut out = Vec::new();
4546
4547 for row in p {
4548 let res = self.base.textify_row(row).0;
4549 let item = match self.gimme_item(res) {
4550 Ok(t) => t,
4551 Err(e) => return Err(e),
4552 };
4553
4554 let creator = item.creator.clone();
4555
4556 out.push((
4557 item,
4558 match self.get_profile(&creator).await {
4559 Ok(ua) => ua,
4560 Err(_) => continue,
4561 },
4562 ));
4563 }
4564
4565 out
4566 }
4567 Err(_) => return Err(DatabaseError::Other),
4568 };
4569
4570 Ok(res)
4572 }
4573
4574 pub async fn create_item(&self, props: ItemCreate, creator: &str) -> Result<Item> {
4581 if props.content.len() > (64 * 128 * 2) {
4583 return Err(DatabaseError::TooLong);
4584 }
4585
4586 if props.content.len() < 2 {
4587 return Err(DatabaseError::ValueError);
4588 }
4589
4590 if props.name.len() > (64 * 2) {
4591 return Err(DatabaseError::TooLong);
4592 }
4593
4594 if props.name.len() < 2 {
4595 return Err(DatabaseError::ValueError);
4596 }
4597
4598 if props.description.len() > (64 * 128) {
4599 return Err(DatabaseError::TooLong);
4600 }
4601
4602 if props.r#type == ItemType::Module && props.cost != -1 {
4604 return Err(DatabaseError::NotAllowed);
4605 }
4606
4607 if props.cost.is_negative() && props.cost != -1 {
4609 return Err(DatabaseError::NotAllowed);
4610 }
4611
4612 let item = Item {
4613 id: AlmostSnowflake::new(self.config.snowflake_server_id).to_string(),
4615 name: props.name,
4616 description: props.description,
4617 cost: props.cost,
4618 content: props.content,
4619 r#type: props.r#type,
4620 status: ItemStatus::Pending,
4621 timestamp: utility::unix_epoch_timestamp(),
4622 creator: creator.to_string(),
4623 };
4624
4625 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
4642 "INSERT INTO \"xugc_items\" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
4643 } else {
4644 "INSERT INTO \"xugc_items\" VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)"
4645 };
4646
4647 let c = &self.base.db.client;
4648 match sqlquery(&query)
4649 .bind::<&str>(&item.id)
4650 .bind::<&str>(&item.name)
4651 .bind::<&str>(&item.description)
4652 .bind::<i32>(item.cost)
4653 .bind::<&str>(&item.content)
4654 .bind::<&str>(&serde_json::to_string(&item.r#type).unwrap())
4655 .bind::<&str>(&serde_json::to_string(&item.status).unwrap())
4656 .bind::<&str>(&item.timestamp.to_string())
4657 .bind::<&str>(&item.creator)
4658 .execute(c)
4659 .await
4660 {
4661 Ok(_) => {
4662 if let Err(e) = self
4664 .create_transaction(
4665 TransactionCreate {
4666 merchant: creator.to_string(),
4667 item: item.id.clone(),
4668 amount: 0,
4669 },
4670 creator,
4671 )
4672 .await
4673 {
4674 return Err(e);
4675 };
4676
4677 return Ok(item);
4679 }
4680 Err(_) => return Err(DatabaseError::Other),
4681 };
4682 }
4683
4684 pub async fn update_item_status(
4691 &self,
4692 id: &str,
4693 status: ItemStatus,
4694 user: Box<Profile>,
4695 ) -> Result<()> {
4696 let item = match self.get_item(id).await {
4698 Ok(i) => i,
4699 Err(e) => return Err(e),
4700 };
4701
4702 let group = match self.get_group_by_id(user.group).await {
4704 Ok(g) => g,
4705 Err(_) => return Err(DatabaseError::Other),
4706 };
4707
4708 if !group.permissions.check(FinePermission::ECON_MASTER) {
4709 return Err(DatabaseError::NotAllowed);
4710 }
4711
4712 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
4714 "UPDATE \"xugc_items\" SET \"status\" = ? WHERE \"id\" = ?"
4715 } else {
4716 "UPDATE \"xugc_items\" SET (\"status\") = ($1) WHERE \"id\" = $2"
4717 };
4718
4719 let c = &self.base.db.client;
4720 match sqlquery(&query)
4721 .bind::<&str>(&serde_json::to_string(&status).unwrap())
4722 .bind::<&str>(&id)
4723 .execute(c)
4724 .await
4725 {
4726 Ok(_) => {
4727 if let Err(e) = self
4728 .create_notification(
4729 NotificationCreate {
4730 title: "Item status updated!".to_string(),
4731 content: format!(
4732 "An item you created has been updated to the status of \"{}\"",
4733 status.to_string()
4734 ),
4735 address: format!("/market/item/{}", item.id.clone()),
4736 recipient: item.creator,
4737 },
4738 None,
4739 )
4740 .await
4741 {
4742 return Err(e);
4743 }
4744
4745 self.base
4747 .cache
4748 .remove(format!("rbeam.auth.econ.item:{}", id))
4749 .await;
4750
4751 return Ok(());
4753 }
4754 Err(_) => return Err(DatabaseError::Other),
4755 };
4756 }
4757
4758 pub async fn update_item(&self, id: &str, props: ItemEdit, user: Box<Profile>) -> Result<()> {
4765 let item = match self.get_item(id).await {
4767 Ok(i) => i,
4768 Err(e) => return Err(e),
4769 };
4770
4771 if props.name.len() > (64 * 2) {
4773 return Err(DatabaseError::TooLong);
4774 }
4775
4776 if props.name.len() < 2 {
4777 return Err(DatabaseError::ValueError);
4778 }
4779
4780 if props.description.len() > (64 * 128) {
4781 return Err(DatabaseError::TooLong);
4782 }
4783
4784 if item.r#type == ItemType::Module && props.cost != -1 {
4786 return Err(DatabaseError::NotAllowed);
4787 }
4788
4789 if props.cost.is_negative() && props.cost != -1 {
4791 return Err(DatabaseError::NotAllowed);
4792 }
4793
4794 let group = match self.get_group_by_id(user.group).await {
4796 Ok(g) => g,
4797 Err(_) => return Err(DatabaseError::Other),
4798 };
4799
4800 if user.id != item.creator {
4801 if !group.permissions.check(FinePermission::ECON_MASTER) {
4802 return Err(DatabaseError::NotAllowed);
4803 }
4804 }
4805
4806 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
4808 "UPDATE \"xugc_items\" SET \"name\" = ?, \"description\" = ?, \"cost\" = ? WHERE \"id\" = ?"
4809 } else {
4810 "UPDATE \"xugc_items\" SET (\"name\", \"description\", \"cost\") = ($1, $2, $3) WHERE \"id\" = $4"
4811 };
4812
4813 let c = &self.base.db.client;
4814 match sqlquery(&query)
4815 .bind::<&str>(&props.name)
4816 .bind::<&str>(&props.description)
4817 .bind::<i32>(props.cost)
4818 .bind::<&str>(&id)
4819 .execute(c)
4820 .await
4821 {
4822 Ok(_) => {
4823 self.base
4825 .cache
4826 .remove(format!("rbeam.auth.econ.item:{}", id))
4827 .await;
4828
4829 return Ok(());
4831 }
4832 Err(_) => return Err(DatabaseError::Other),
4833 };
4834 }
4835
4836 pub async fn update_item_content(
4843 &self,
4844 id: &str,
4845 props: ItemEditContent,
4846 user: Box<Profile>,
4847 ) -> Result<()> {
4848 let item = match self.get_item(id).await {
4850 Ok(i) => i,
4851 Err(e) => return Err(e),
4852 };
4853
4854 if item.r#type == ItemType::Module {
4860 return Err(DatabaseError::NotAllowed);
4861 }
4862
4863 if props.content.len() > (64 * 128 * 2) {
4865 return Err(DatabaseError::TooLong);
4866 }
4867
4868 if props.content.len() < 2 {
4869 return Err(DatabaseError::ValueError);
4870 }
4871
4872 let group = match self.get_group_by_id(user.group).await {
4874 Ok(g) => g,
4875 Err(_) => return Err(DatabaseError::Other),
4876 };
4877
4878 if user.id != item.creator {
4879 if !group.permissions.check(FinePermission::ECON_MASTER) {
4880 return Err(DatabaseError::NotAllowed);
4881 }
4882 }
4883
4884 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
4886 "UPDATE \"xugc_items\" SET \"content\" = ? WHERE \"id\" = ?"
4887 } else {
4888 "UPDATE \"xugc_items\" SET (\"content\") = ($1) WHERE \"id\" = $2"
4889 };
4890
4891 let c = &self.base.db.client;
4892 match sqlquery(&query)
4893 .bind::<&str>(&props.content)
4894 .bind::<&str>(&id)
4895 .execute(c)
4896 .await
4897 {
4898 Ok(_) => {
4899 self.base
4901 .cache
4902 .remove(format!("rbeam.auth.econ.item:{}", id))
4903 .await;
4904
4905 return Ok(());
4907 }
4908 Err(_) => return Err(DatabaseError::Other),
4909 };
4910 }
4911
4912 pub async fn delete_item(&self, id: &str, user: Box<Profile>) -> Result<()> {
4920 let item = match self.get_item(id).await {
4922 Ok(n) => n,
4923 Err(e) => return Err(e),
4924 };
4925
4926 if item.creator != user.id {
4928 let group = match self.get_group_by_id(user.group).await {
4930 Ok(g) => g,
4931 Err(_) => return Err(DatabaseError::Other),
4932 };
4933
4934 if !group.permissions.check(FinePermission::ECON_MASTER) {
4935 return Err(DatabaseError::NotAllowed);
4936 }
4937 }
4938
4939 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
4941 "DELETE FROM \"xugc_items\" WHERE \"id\" = ?"
4942 } else {
4943 "DELETE FROM \"xugc_items\" WHERE \"id\" = $1"
4944 };
4945
4946 let c = &self.base.db.client;
4947 match sqlquery(&query).bind::<&str>(&id).execute(c).await {
4948 Ok(_) => {
4949 self.base
4951 .cache
4952 .remove(format!("rbeam.auth.econ.item:{}", id))
4953 .await;
4954
4955 return Ok(());
4957 }
4958 Err(_) => return Err(DatabaseError::Other),
4959 };
4960 }
4961
4962 pub async fn update_profile_totp_secret(
4966 &self,
4967 id: &str,
4968 secret: &str,
4969 recovery: &Vec<String>,
4970 ) -> Result<()> {
4971 let ua = match self.get_profile_by_id(&id).await {
4972 Ok(ua) => ua,
4973 Err(e) => return Err(e),
4974 };
4975
4976 let query = if (self.base.db.r#type == "sqlite") | (self.base.db.r#type == "mysql") {
4978 "UPDATE \"xprofiles\" SET \"totp\" = ?, \"recovery_codes\" = ? WHERE \"id\" = ?"
4979 } else {
4980 "UPDATE \"xprofiles\" SET (\"totp\", \"recovery_codes\") = ($1, $2) WHERE \"id\" = $3"
4981 };
4982
4983 let c = &self.base.db.client;
4984 match sqlquery(&query)
4985 .bind::<&str>(&secret)
4986 .bind::<&str>(&serde_json::to_string(&recovery).unwrap())
4987 .bind::<&str>(&id)
4988 .execute(c)
4989 .await
4990 {
4991 Ok(_) => {
4992 self.base
4993 .cache
4994 .remove(format!("rbeam.auth.profile:{id}"))
4995 .await;
4996
4997 self.base
4998 .cache
4999 .remove(format!("rbeam.auth.profile:{}", ua.username))
5000 .await;
5001
5002 Ok(())
5003 }
5004 Err(_) => Err(DatabaseError::Other),
5005 }
5006 }
5007
5008 pub async fn enable_totp(
5013 &self,
5014 as_user: Box<Profile>,
5015 id: &str,
5016 ) -> Result<(String, String, Vec<String>)> {
5017 let profile = match self.get_profile(&id).await {
5018 Ok(p) => p,
5019 Err(e) => return Err(e),
5020 };
5021
5022 if profile.id != as_user.id {
5023 return Err(DatabaseError::NotAllowed);
5024 }
5025
5026 let secret = totp_rs::Secret::default().to_string();
5027 let recovery = Database::generate_totp_recovery_codes();
5028
5029 if let Err(e) = self
5031 .update_profile_totp_secret(id, &secret, &recovery)
5032 .await
5033 {
5034 return Err(e);
5035 }
5036
5037 let profile = match self.get_profile(&id).await {
5039 Ok(p) => p,
5040 Err(e) => return Err(e),
5041 };
5042
5043 let totp = profile.totp(Some(
5045 self.config
5046 .host
5047 .replace("http://", "")
5048 .replace("https://", "")
5049 .replace(":", "_"),
5050 ));
5051
5052 if totp.is_none() {
5053 return Err(DatabaseError::Other);
5054 }
5055
5056 let totp = totp.unwrap();
5057
5058 let qr = match totp.get_qr_base64() {
5060 Ok(q) => q,
5061 Err(_) => return Err(DatabaseError::Other),
5062 };
5063
5064 Ok((totp.get_secret_base32(), qr, recovery))
5066 }
5067
5068 pub fn check_totp(&self, ua: &Box<Profile>, code: &str) -> bool {
5070 let totp = ua.totp(Some(
5071 self.config
5072 .host
5073 .replace("http://", "")
5074 .replace("https://", "")
5075 .replace(":", "_"),
5076 ));
5077
5078 if let Some(totp) = totp {
5079 return !code.is_empty()
5080 && (totp.check_current(code).unwrap()
5081 | ua.recovery_codes.contains(&code.to_string()));
5082 }
5083
5084 true
5085 }
5086
5087 pub fn generate_totp_recovery_codes() -> Vec<String> {
5089 let mut out: Vec<String> = Vec::new();
5090
5091 for _ in 0..9 {
5092 out.push(rainbeam_shared::hash::salt())
5093 }
5094
5095 out
5096 }
5097}