1use super::common::NAME_REGEX;
2use oiseau::cache::Cache;
3use crate::model::{
4 Error, Result,
5 auth::{
6 ACHIEVEMENTS, Achievement, AchievementName, AchievementRarity, Notification, Token, User,
7 UserConnections, UserSettings,
8 },
9 id::Id,
10 moderation::AuditLogEntry,
11 oauth::AuthGrant,
12 permissions::{FinePermission, PermissionsContainer},
13};
14use tritools::{
15 time::unix_epoch_timestamp,
16 encoding::{hash_salted, salt},
17};
18use crate::{auto_method, DataManager};
19use oiseau::{PostgresRow, execute, get, query_row, params};
20
21impl DataManager {
22 pub(crate) fn get_user_from_row(x: &PostgresRow) -> User {
24 let mut u = User {
25 id: crate::model::id::Id::deserialize(&get!(x->0(String))),
26 created: get!(x->1(i64)) as u128,
27 username: get!(x->2(String)),
28 password: get!(x->3(String)),
29 salt: get!(x->4(String)),
30 settings: serde_json::from_str(&get!(x->5(String)).to_string()).unwrap(),
31 tokens: serde_json::from_str(&get!(x->6(String)).to_string()).unwrap(),
32 legacy_permissions: get!(x->7(i32)),
33 is_verified: get!(x->8(i32)) as i8 == 1,
34 notification_count: get!(x->9(i32)) as usize,
35 follower_count: get!(x->10(i32)) as usize,
36 following_count: get!(x->11(i32)) as usize,
37 last_seen: get!(x->12(i64)) as u128,
38 totp: get!(x->13(String)),
39 recovery_codes: serde_json::from_str(&get!(x->14(String)).to_string()).unwrap(),
40 post_count: get!(x->15(i32)) as usize,
41 request_count: get!(x->16(i32)) as usize,
42 connections: serde_json::from_str(&get!(x->17(String)).to_string()).unwrap(),
43 stripe_id: get!(x->18(String)),
44 grants: serde_json::from_str(&get!(x->19(String)).to_string()).unwrap(),
45 associated: serde_json::from_str(&get!(x->20(String)).to_string()).unwrap(),
46 achievements: serde_json::from_str(&get!(x->21(String)).to_string()).unwrap(),
47 ban_reason: get!(x->22(String)),
48 is_deactivated: get!(x->23(i32)) as i8 == 1,
49 ban_expire: get!(x->24(i64)) as u128,
50 checkouts: serde_json::from_str(&get!(x->25(String)).to_string()).unwrap(),
51 last_policy_consent: get!(x->26(i64)) as u128,
52 missed_messages_count: get!(x->27(i32)) as usize,
53 views: get!(x->28(i32)) as usize,
54 shrimpcamp_link: get!(x->29(i64)) as usize,
55 permissions: serde_json::from_str(&get!(x->30(String))).unwrap(),
56 };
57
58 if (u.legacy_permissions & 1 << 17) == 1 << 17 {
60 u.permissions.join(FinePermission::Banned);
62 }
63
64 if ((u.legacy_permissions & 1 << 19) == 1 << 19)
65 || ((u.legacy_permissions & 1 << 1) == 1 << 1)
66 {
67 u.permissions.join(FinePermission::Supporter);
69 }
70
71 u
73 }
74
75 auto_method!(get_user_by_id()@get_user_from_row -> "SELECT * FROM users WHERE id = $1" --name="user" --returns=User --cache-key-tmpl="atto.user:{}");
76 auto_method!(get_user_by_username(&str)@get_user_from_row -> "SELECT * FROM users WHERE username = $1" --name="user" --returns=User --cache-key-tmpl="atto.user:{}");
77 auto_method!(get_user_by_username_no_cache(&str)@get_user_from_row -> "SELECT * FROM users WHERE username = $1" --name="user" --returns=User);
78 auto_method!(get_user_by_browser_session(&str)@get_user_from_row -> "SELECT * FROM users WHERE browser_session = $1" --name="user" --returns=User);
79
80 pub async fn get_user_by_id_with_void(&self, id: &crate::model::id::Id) -> Result<User> {
85 let conn = match self.0.connect().await {
86 Ok(c) => c,
87 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
88 };
89
90 let res = query_row!(
91 &conn,
92 "SELECT * FROM users WHERE id = $1",
93 &[&id.printable()],
94 |x| Ok(Self::get_user_from_row(x))
95 );
96
97 if res.is_err() {
98 return Ok(User::deleted());
99 }
101
102 Ok(res.unwrap())
103 }
104
105 pub async fn get_user_by_token(&self, token: &str) -> Result<User> {
110 let conn = match self.0.connect().await {
111 Ok(c) => c,
112 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
113 };
114
115 let res = query_row!(
116 &conn,
117 "SELECT * FROM users WHERE tokens LIKE $1",
118 &[&format!("%\"{token}\"%")],
119 |x| Ok(Self::get_user_from_row(x))
120 );
121
122 if res.is_err() {
123 return Err(Error::UserNotFound);
124 }
125
126 let u = res.unwrap();
127
128 if u.legacy_permissions != 0
129 && (u.permissions.check(FinePermission::Banned)
130 || u.permissions.check(FinePermission::Supporter))
131 {
132 self.update_user_legacy_permissions(&u.id, 0).await?;
134 self.update_user_permissions(&u.id, u.permissions.clone())
135 .await?;
136 }
137
138 Ok(u)
139 }
140
141 pub async fn get_user_by_grant_token(
148 &self,
149 token: &Id,
150 check_expiration: bool,
151 ) -> Result<(AuthGrant, User)> {
152 let conn = match self.0.connect().await {
153 Ok(c) => c,
154 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
155 };
156
157 let res = query_row!(
158 &conn,
159 "SELECT * FROM users WHERE grants LIKE $1",
160 &[&format!("%\"token\":\"{token}\"%")],
161 |x| Ok(Self::get_user_from_row(x))
162 );
163
164 if res.is_err() {
165 return Err(Error::UserNotFound);
166 }
167
168 let user = res.unwrap();
169 let grant = user
170 .grants
171 .iter()
172 .find(|x| x.token == *token)
173 .unwrap()
174 .clone();
175
176 if check_expiration {
178 let now = unix_epoch_timestamp();
179 let delta = now - grant.last_updated;
180
181 if delta > 604_800_000 {
182 return Err(Error::MiscError("Token expired".to_string()));
183 }
184 }
185
186 Ok((grant, user))
188 }
189
190 pub async fn create_user(&self, mut data: User) -> Result<()> {
195 if !self.0.0.security.registration_enabled {
196 return Err(Error::RegistrationDisabled);
197 }
198
199 data.username = data.username.to_lowercase();
200
201 if data.username.len() < 2 {
203 return Err(Error::DataTooShort("username".to_string()));
204 } else if data.username.len() > 32 {
205 return Err(Error::DataTooLong("username".to_string()));
206 }
207
208 if data.password.len() < 6 {
209 return Err(Error::DataTooShort("password".to_string()));
210 }
211
212 if self.0.0.banned_usernames.contains(&data.username) {
213 return Err(Error::MiscError("This username cannot be used".to_string()));
214 }
215
216 let regex = regex::RegexBuilder::new(NAME_REGEX)
217 .multi_line(true)
218 .build()
219 .unwrap();
220
221 if regex.captures(&data.username).is_some() {
222 return Err(Error::MiscError(
223 "This username contains invalid characters".to_string(),
224 ));
225 }
226
227 if self.get_user_by_username(&data.username).await.is_ok() {
229 return Err(Error::UsernameInUse);
230 }
231
232 let conn = match self.0.connect().await {
234 Ok(c) => c,
235 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
236 };
237
238 let res = execute!(
239 &conn,
240 "INSERT INTO users VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31)",
241 params![
242 &data.id.printable(),
243 &(data.created as i64),
244 &data.username.to_lowercase(),
245 &data.password,
246 &data.salt,
247 &serde_json::to_string(&data.settings).unwrap(),
248 &serde_json::to_string(&data.tokens).unwrap(),
249 &0_i32,
250 &if data.is_verified { 1_i32 } else { 0_i32 },
251 &0_i32,
252 &0_i32,
253 &0_i32,
254 &(data.last_seen as i64),
255 &String::new(),
256 "[]",
257 &0_i32,
258 &0_i32,
259 &serde_json::to_string(&data.connections).unwrap(),
260 &"",
261 &serde_json::to_string(&data.grants).unwrap(),
262 &serde_json::to_string(&data.associated).unwrap(),
263 &serde_json::to_string(&data.achievements).unwrap(),
264 &data.ban_reason,
265 &if data.is_deactivated { 1_i32 } else { 0_i32 },
266 &(data.ban_expire as i64),
267 &serde_json::to_string(&data.checkouts).unwrap(),
268 &(data.last_policy_consent as i64),
269 &(data.missed_messages_count as i32),
270 &(data.views as i32),
271 &(data.shrimpcamp_link as i64),
272 &serde_json::to_string(&data.permissions).unwrap()
273 ]
274 );
275
276 if let Err(e) = res {
277 return Err(Error::DatabaseError(e.to_string()));
278 }
279
280 Ok(())
281 }
282
283 pub async fn delete_user(&self, id: &Id, password: &str, force: bool) -> Result<User> {
290 let user = self.get_user_by_id(id).await?;
291
292 if (hash_salted(password.to_string(), user.salt.clone()) != user.password) && !force {
293 return Err(Error::IncorrectPassword);
294 }
295
296 if user.permissions.check(FinePermission::Supporter) {
297 return Err(Error::MiscError(
298 "Please cancel your supporter membership before attempting to delete your account"
299 .to_string(),
300 ));
301 }
302
303 let conn = match self.0.connect().await {
304 Ok(c) => c,
305 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
306 };
307
308 let res = execute!(&conn, "DELETE FROM users WHERE id = $1", &[&id.printable()]);
309
310 if let Err(e) = res {
311 return Err(Error::DatabaseError(e.to_string()));
312 }
313
314 self.cache_clear_user(&user).await;
315
316 for upload in match self.1.get_uploads_by_owner_all(user.id.as_usize()).await {
318 Ok(x) => x,
319 Err(e) => return Err(Error::MiscError(e.to_string())),
320 } {
321 if let Err(e) = self.1.delete_upload(upload.id).await {
322 return Err(Error::MiscError(e.to_string()));
323 }
324 }
325
326 Ok(user)
328 }
329
330 pub async fn update_user_verified_status(&self, id: &Id, x: bool, user: User) -> Result<()> {
331 if !user.permissions.check(FinePermission::ManageVerified) {
332 return Err(Error::NotAllowed);
333 }
334
335 let other_user = self.get_user_by_id(id).await?;
336
337 let conn = match self.0.connect().await {
338 Ok(c) => c,
339 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
340 };
341
342 let res = execute!(
343 &conn,
344 "UPDATE users SET verified = $1 WHERE id = $2",
345 params![&{ if x { 1 } else { 0 } }, &id.printable()]
346 );
347
348 if let Err(e) = res {
349 return Err(Error::DatabaseError(e.to_string()));
350 }
351
352 self.cache_clear_user(&other_user).await;
353
354 self.create_audit_log_entry(AuditLogEntry::new(
356 user.id,
357 format!(
358 "invoked `update_user_verified_status` with x value `{}` and y value `{}`",
359 other_user.id, x
360 ),
361 ))
362 .await?;
363
364 Ok(())
366 }
367
368 pub async fn update_user_is_deactivated(
369 &self,
370 id: &crate::model::id::Id,
371 x: bool,
372 user: User,
373 ) -> Result<()> {
374 if *id != user.id && !user.permissions.check(FinePermission::ManageUsers) {
375 return Err(Error::NotAllowed);
376 }
377
378 let other_user = self.get_user_by_id(id).await?;
379
380 let conn = match self.0.connect().await {
381 Ok(c) => c,
382 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
383 };
384
385 let res = execute!(
386 &conn,
387 "UPDATE users SET is_deactivated = $1 WHERE id = $2",
388 params![&{ if x { 1 } else { 0 } }, &id.printable()]
389 );
390
391 if let Err(e) = res {
392 return Err(Error::DatabaseError(e.to_string()));
393 }
394
395 self.cache_clear_user(&other_user).await;
396
397 if user.id != other_user.id {
399 self.create_audit_log_entry(AuditLogEntry::new(
400 user.id,
401 format!(
402 "invoked `update_user_is_deactivated` with x value `{}` and y value `{}`",
403 other_user.id, x
404 ),
405 ))
406 .await?;
407 }
408
409 Ok(())
411 }
412
413 pub async fn update_user_password(
414 &self,
415 id: &crate::model::id::Id,
416 from: String,
417 to: String,
418 user: User,
419 force: bool,
420 ) -> Result<()> {
421 if !user.check_password(from.clone()) && !force {
423 return Err(Error::MiscError("Password does not match".to_string()));
424 }
425
426 let conn = match self.0.connect().await {
428 Ok(c) => c,
429 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
430 };
431
432 let new_salt = salt();
433 let new_password = hash_salted(to, new_salt.clone());
434 let res = execute!(
435 &conn,
436 "UPDATE users SET password = $1, salt = $2 WHERE id = $3",
437 params![&new_password.as_str(), &new_salt.as_str(), &id.printable()]
438 );
439
440 if let Err(e) = res {
441 return Err(Error::DatabaseError(e.to_string()));
442 }
443
444 self.cache_clear_user(&user).await;
445 Ok(())
446 }
447
448 pub async fn update_user_username(
449 &self,
450 id: &crate::model::id::Id,
451 to: String,
452 user: User,
453 ) -> Result<()> {
454 if to.len() < 2 {
456 return Err(Error::DataTooShort("username".to_string()));
457 } else if to.len() > 32 {
458 return Err(Error::DataTooLong("username".to_string()));
459 }
460
461 if self.0.0.banned_usernames.contains(&to) {
462 return Err(Error::MiscError("This username cannot be used".to_string()));
463 }
464
465 let regex = regex::RegexBuilder::new(r"[^\w_\-\.!]+")
466 .multi_line(true)
467 .build()
468 .unwrap();
469
470 if regex.captures(&to).is_some() {
471 return Err(Error::MiscError(
472 "This username contains invalid characters".to_string(),
473 ));
474 }
475
476 let conn = match self.0.connect().await {
478 Ok(c) => c,
479 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
480 };
481
482 let res = execute!(
483 &conn,
484 "UPDATE users SET username = $1 WHERE id = $2",
485 params![&to.to_lowercase(), &id.printable()]
486 );
487
488 if let Err(e) = res {
489 return Err(Error::DatabaseError(e.to_string()));
490 }
491
492 self.cache_clear_user(&user).await;
493 Ok(())
494 }
495
496 pub async fn update_user_awaiting_purchased_status(
497 &self,
498 id: &crate::model::id::Id,
499 x: bool,
500 user: User,
501 require_permission: bool,
502 ) -> Result<()> {
503 if (user.id != *id) | require_permission
504 && !user.permissions.check(FinePermission::ManageUsers)
505 {
506 return Err(Error::NotAllowed);
507 }
508
509 let other_user = self.get_user_by_id(id).await?;
510
511 let conn = match self.0.connect().await {
512 Ok(c) => c,
513 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
514 };
515
516 let res = execute!(
517 &conn,
518 "UPDATE users SET awaiting_purchase = $1 WHERE id = $2",
519 params![&{ if x { 1 } else { 0 } }, &id.printable()]
520 );
521
522 if let Err(e) = res {
523 return Err(Error::DatabaseError(e.to_string()));
524 }
525
526 self.cache_clear_user(&other_user).await;
527
528 if user.id != other_user.id {
530 self.create_audit_log_entry(AuditLogEntry::new(
531 user.id,
532 format!(
533 "invoked `update_user_purchased_status` with x value `{}` and y value `{}`",
534 other_user.id, x
535 ),
536 ))
537 .await?;
538 }
539
540 Ok(())
542 }
543
544 pub async fn seen_user(&self, user: &User) -> Result<()> {
545 let conn = match self.0.connect().await {
546 Ok(c) => c,
547 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
548 };
549
550 let res = execute!(
551 &conn,
552 "UPDATE users SET last_seen = $1 WHERE id = $2",
553 params![&(unix_epoch_timestamp() as i64), &user.id.printable()]
554 );
555
556 if let Err(e) = res {
557 return Err(Error::DatabaseError(e.to_string()));
558 }
559
560 self.cache_clear_user(user).await;
561
562 Ok(())
563 }
564
565 #[async_recursion::async_recursion]
569 pub async fn add_achievement(
570 &self,
571 user: &mut User,
572 achievement: Achievement,
573 check_for_final: bool,
574 ) -> Result<()> {
575 if user.settings.disable_achievements {
576 return Ok(());
577 }
578
579 if user.achievements.iter().any(|x| x.name == achievement.name) {
580 return Ok(());
581 }
582
583 self.create_notification(Notification::new(
585 "You've earned a new achievement!".to_string(),
586 format!(
587 "You've earned the \"{}\" [achievement](/user/achievements)!",
588 achievement.name.title()
589 ),
590 user.id.to_owned(),
591 ))
592 .await?;
593
594 user.achievements.push(achievement);
596 self.update_user_achievements(&user.id, user.achievements.to_owned())
597 .await?;
598
599 if check_for_final && user.achievements.len() + 1 == ACHIEVEMENTS {
601 self.add_achievement(user, AchievementName::GetAllOtherAchievements.into(), false)
602 .await?;
603 }
604
605 Ok(())
607 }
608
609 pub fn fill_achievements(
614 &self,
615 mut list: Vec<Achievement>,
616 ) -> Vec<(String, String, AchievementRarity, Achievement)> {
617 let mut out = Vec::new();
618
619 list.sort_by(|a, b| a.unlocked.cmp(&b.unlocked));
621 list.reverse();
622
623 for x in list {
625 out.push((
626 x.name.title().to_string(),
627 x.name.description().to_string(),
628 x.name.rarity(),
629 x,
630 ))
631 }
632
633 out
634 }
635
636 pub fn check_totp(&self, ua: &User, code: &str) -> bool {
638 let totp = ua.totp(Some(
639 self.0
640 .0
641 .host
642 .replace("http://", "")
643 .replace("https://", "")
644 .replace(":", "_"),
645 ));
646
647 if let Some(totp) = totp {
648 return !code.is_empty()
649 && (totp.check_current(code).unwrap()
650 | ua.recovery_codes.contains(&code.to_string()));
651 }
652
653 true
654 }
655
656 pub fn generate_totp_recovery_codes() -> Vec<String> {
658 let mut out: Vec<String> = Vec::new();
659
660 for _ in 0..9 {
661 out.push(salt())
662 }
663
664 out
665 }
666
667 pub async fn update_user_totp(
674 &self,
675 id: &crate::model::id::Id,
676 secret: &str,
677 recovery: &Vec<String>,
678 ) -> Result<()> {
679 let user = self.get_user_by_id(id).await?;
680
681 let conn = match self.0.connect().await {
683 Ok(c) => c,
684 Err(e) => return Err(Error::DatabaseConnection(e.to_string())),
685 };
686
687 let res = execute!(
688 &conn,
689 "UPDATE users SET totp = $1, recovery_codes = $2 WHERE id = $3",
690 params![
691 &secret,
692 &serde_json::to_string(recovery).unwrap(),
693 &id.printable()
694 ]
695 );
696
697 if let Err(e) = res {
698 return Err(Error::DatabaseError(e.to_string()));
699 }
700
701 self.cache_clear_user(&user).await;
702 Ok(())
703 }
704
705 pub async fn enable_totp(
714 &self,
715 id: &crate::model::id::Id,
716 user: User,
717 ) -> Result<(String, String, Vec<String>)> {
718 let other_user = self.get_user_by_id(id).await?;
719
720 if other_user.id != user.id {
721 if other_user.permissions.check(FinePermission::ManageUsers) {
722 self.create_audit_log_entry(AuditLogEntry::new(
724 user.id,
725 format!("invoked `enable_totp` with x value `{}`", other_user.id,),
726 ))
727 .await?;
728 } else {
729 return Err(Error::NotAllowed);
730 }
731 }
732
733 let secret = totp_rs::Secret::default().to_string();
734 let recovery = Self::generate_totp_recovery_codes();
735 self.update_user_totp(id, &secret, &recovery).await?;
736
737 let other_user = self.get_user_by_id(id).await?;
739
740 let totp = other_user.totp(Some(
742 self.0
743 .0
744 .host
745 .replace("http://", "")
746 .replace("https://", "")
747 .replace(":", "_"),
748 ));
749
750 if totp.is_none() {
751 return Err(Error::MiscError("Failed to get TOTP code".to_string()));
752 }
753
754 let totp = totp.unwrap();
755
756 let qr = match totp.get_qr_base64() {
758 Ok(q) => q,
759 Err(e) => return Err(Error::MiscError(e.to_string())),
760 };
761
762 Ok((totp.get_secret_base32(), qr, recovery))
764 }
765
766 pub async fn cache_clear_user(&self, user: &User) {
767 self.0.1.remove(format!("atto.user:{}", user.id)).await;
768 self.0
769 .1
770 .remove(format!("atto.user:{}", user.username))
771 .await;
772 }
773
774 auto_method!(update_user_permissions(PermissionsContainer)@get_user_by_id -> "UPDATE users SET permissions = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
775 auto_method!(update_user_tokens(Vec<Token>)@get_user_by_id -> "UPDATE users SET tokens = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
776 auto_method!(update_user_grants(Vec<AuthGrant>)@get_user_by_id -> "UPDATE users SET grants = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
777 auto_method!(update_user_settings(UserSettings)@get_user_by_id -> "UPDATE users SET settings = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
778 auto_method!(update_user_connections(UserConnections)@get_user_by_id -> "UPDATE users SET connections = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
779 auto_method!(update_user_associated(Vec<Id>)@get_user_by_id -> "UPDATE users SET associated = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
780 auto_method!(update_user_achievements(Vec<Achievement>)@get_user_by_id -> "UPDATE users SET achievements = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
781 auto_method!(update_user_ban_reason(&str)@get_user_by_id -> "UPDATE users SET ban_reason = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
782 auto_method!(update_user_channel_mutes(Vec<usize>)@get_user_by_id -> "UPDATE users SET channel_mutes = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
783 auto_method!(update_user_ban_expire(i64)@get_user_by_id -> "UPDATE users SET ban_expire = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
784 auto_method!(update_user_checkouts(Vec<String>)@get_user_by_id -> "UPDATE users SET checkouts = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
785 auto_method!(update_user_last_policy_consent(i64)@get_user_by_id -> "UPDATE users SET last_policy_consent = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
786 auto_method!(update_user_close_friends_stack(i64)@get_user_by_id -> "UPDATE users SET close_friends_stack = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
787 auto_method!(update_user_shrimpcamp_link(i64)@get_user_by_id -> "UPDATE users SET shrimpcamp_link = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
788 auto_method!(update_user_legacy_permissions(i32)@get_user_by_id -> "UPDATE users SET legacy_permissions = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
789
790 auto_method!(get_user_by_stripe_id(&str)@get_user_from_row -> "SELECT * FROM users WHERE stripe_id = $1" --name="user" --returns=User);
791 auto_method!(update_user_stripe_id(&str)@get_user_by_id -> "UPDATE users SET stripe_id = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
792
793 auto_method!(update_user_notification_count(i32)@get_user_by_id -> "UPDATE users SET notification_count = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
794 auto_method!(incr_user_notifications()@get_user_by_id -> "UPDATE users SET notification_count = notification_count + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --incr);
795 auto_method!(decr_user_notifications()@get_user_by_id -> "UPDATE users SET notification_count = notification_count - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --decr=notification_count);
796
797 auto_method!(incr_user_follower_count()@get_user_by_id -> "UPDATE users SET follower_count = follower_count + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --incr);
798 auto_method!(decr_user_follower_count()@get_user_by_id -> "UPDATE users SET follower_count = follower_count - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --decr=follower_count);
799
800 auto_method!(incr_user_following_count()@get_user_by_id -> "UPDATE users SET following_count = following_count + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --incr);
801 auto_method!(decr_user_following_count()@get_user_by_id -> "UPDATE users SET following_count = following_count - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --decr=following_count);
802
803 auto_method!(incr_user_post_count()@get_user_by_id -> "UPDATE users SET post_count = post_count + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --incr);
804 auto_method!(decr_user_post_count()@get_user_by_id -> "UPDATE users SET post_count = post_count - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --decr=post_count);
805
806 auto_method!(update_user_request_count(i32)@get_user_by_id -> "UPDATE users SET request_count = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
807 auto_method!(incr_user_request_count()@get_user_by_id -> "UPDATE users SET request_count = request_count + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --incr);
808 auto_method!(decr_user_request_count()@get_user_by_id -> "UPDATE users SET request_count = request_count - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --decr=request_count);
809
810 auto_method!(update_user_missed_messages_count(i32)@get_user_by_id -> "UPDATE users SET missed_messages_count = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
811 auto_method!(incr_user_missed_messages()@get_user_by_id -> "UPDATE users SET missed_messages_count = missed_messages_count + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --incr);
812 auto_method!(decr_user_missed_messages()@get_user_by_id -> "UPDATE users SET missed_messages_count = missed_messages_count - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --decr=notification_count);
813
814 auto_method!(incr_profile_views()@get_user_by_id -> "UPDATE users SET views = views + 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --incr);
815 auto_method!(decr_profile_views()@get_user_by_id -> "UPDATE users SET views = views - 1 WHERE id = $1" --cache-key-tmpl=cache_clear_user --decr=views);
816}