2025-03-22 22:17:47 -04:00
use super ::* ;
2025-03-23 21:19:16 -04:00
use crate ::cache ::Cache ;
2025-04-26 16:27:18 -04:00
use crate ::model ::auth ::UserConnections ;
2025-04-02 11:39:51 -04:00
use crate ::model ::moderation ::AuditLogEntry ;
2025-03-23 18:03:11 -04:00
use crate ::model ::{
Error , Result ,
2025-03-25 23:58:27 -04:00
auth ::{ Token , User , UserSettings } ,
2025-03-23 18:03:11 -04:00
permissions ::FinePermission ,
} ;
2025-04-03 15:07:57 -04:00
use crate ::{ auto_method , execute , get , query_row , params } ;
2025-04-01 16:12:13 -04:00
use pathbufd ::PathBufD ;
use std ::fs ::{ exists , remove_file } ;
2025-03-31 11:45:34 -04:00
use tetratto_shared ::hash ::{ hash_salted , salt } ;
2025-04-02 14:11:01 -04:00
use tetratto_shared ::unix_epoch_timestamp ;
2025-03-22 22:17:47 -04:00
#[ cfg(feature = " sqlite " ) ]
use rusqlite ::Row ;
#[ cfg(feature = " postgres " ) ]
use tokio_postgres ::Row ;
impl DataManager {
/// Get a [`User`] from an SQL row.
pub ( crate ) fn get_user_from_row (
#[ cfg(feature = " sqlite " ) ] x : & Row < '_ > ,
#[ cfg(feature = " postgres " ) ] x : & Row ,
) -> User {
User {
2025-04-03 13:52:29 -04:00
id : get ! ( x ->0 ( i64 ) ) as usize ,
created : get ! ( x ->1 ( i64 ) ) as usize ,
2025-03-22 22:17:47 -04:00
username : get ! ( x ->2 ( String ) ) ,
password : get ! ( x ->3 ( String ) ) ,
salt : get ! ( x ->4 ( String ) ) ,
settings : serde_json ::from_str ( & get! ( x ->5 ( String ) ) . to_string ( ) ) . unwrap ( ) ,
tokens : serde_json ::from_str ( & get! ( x ->6 ( String ) ) . to_string ( ) ) . unwrap ( ) ,
2025-04-03 15:56:44 -04:00
permissions : FinePermission ::from_bits ( get! ( x ->7 ( i32 ) ) as u32 ) . unwrap ( ) ,
2025-04-10 18:16:52 -04:00
is_verified : get ! ( x ->8 ( i32 ) ) as i8 = = 1 ,
2025-04-03 15:56:44 -04:00
notification_count : get ! ( x ->9 ( i32 ) ) as usize ,
follower_count : get ! ( x ->10 ( i32 ) ) as usize ,
following_count : get ! ( x ->11 ( i32 ) ) as usize ,
2025-04-03 13:52:29 -04:00
last_seen : get ! ( x ->12 ( i64 ) ) as usize ,
2025-04-04 21:42:08 -04:00
totp : get ! ( x ->13 ( String ) ) ,
recovery_codes : serde_json ::from_str ( & get! ( x ->14 ( String ) ) . to_string ( ) ) . unwrap ( ) ,
2025-04-12 22:25:54 -04:00
post_count : get ! ( x ->15 ( i32 ) ) as usize ,
request_count : get ! ( x ->16 ( i32 ) ) as usize ,
2025-04-26 16:27:18 -04:00
connections : serde_json ::from_str ( & get! ( x ->17 ( String ) ) . to_string ( ) ) . unwrap ( ) ,
2025-03-22 22:17:47 -04:00
}
}
2025-04-03 15:07:57 -04:00
auto_method! ( get_user_by_id ( usize as i64 ) @ get_user_from_row -> " SELECT * FROM users WHERE id = $1 " - - name = " user " - - returns = User - - cache - key - tmpl = " atto.user:{} " ) ;
2025-03-23 21:19:16 -04:00
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:{} " ) ;
2025-03-22 22:17:47 -04:00
2025-04-12 22:25:54 -04:00
/// Get a user given just their ID. Returns the void user if the user doesn't exist.
///
/// # Arguments
/// * `id` - the ID of the user
pub async fn get_user_by_id_with_void ( & self , id : usize ) -> Result < User > {
let conn = match self . connect ( ) . await {
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let res = query_row! (
& conn ,
" SELECT * FROM users WHERE id = $1 " ,
& [ & ( id as i64 ) ] ,
| x | Ok ( Self ::get_user_from_row ( x ) )
) ;
if res . is_err ( ) {
return Ok ( User ::deleted ( ) ) ;
// return Err(Error::UserNotFound);
}
Ok ( res . unwrap ( ) )
}
2025-03-22 22:17:47 -04:00
/// Get a user given just their auth token.
///
/// # Arguments
/// * `token` - the token of the user
pub async fn get_user_by_token ( & self , token : & str ) -> Result < User > {
let conn = match self . connect ( ) . await {
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let res = query_row! (
& conn ,
" SELECT * FROM users WHERE tokens LIKE $1 " ,
& [ & format! ( " % \" {token} \" % " ) ] ,
| x | Ok ( Self ::get_user_from_row ( x ) )
) ;
if res . is_err ( ) {
return Err ( Error ::UserNotFound ) ;
}
Ok ( res . unwrap ( ) )
}
/// Create a new user in the database.
///
/// # Arguments
/// * `data` - a mock [`User`] object to insert
2025-04-03 16:47:48 -04:00
pub async fn create_user ( & self , mut data : User ) -> Result < ( ) > {
2025-04-10 18:16:52 -04:00
if ! self . 0. security . registration_enabled {
2025-03-22 22:17:47 -04:00
return Err ( Error ::RegistrationDisabled ) ;
}
2025-04-03 16:47:48 -04:00
data . username = data . username . to_lowercase ( ) ;
2025-03-22 22:17:47 -04:00
// check values
if data . username . len ( ) < 2 {
return Err ( Error ::DataTooShort ( " username " . to_string ( ) ) ) ;
} else if data . username . len ( ) > 32 {
return Err ( Error ::DataTooLong ( " username " . to_string ( ) ) ) ;
}
if data . password . len ( ) < 6 {
return Err ( Error ::DataTooShort ( " password " . to_string ( ) ) ) ;
}
2025-03-31 22:35:11 -04:00
if self . 0. banned_usernames . contains ( & data . username ) {
return Err ( Error ::MiscError ( " This username cannot be used " . to_string ( ) ) ) ;
}
2025-03-30 22:33:07 -04:00
// make sure username isn't taken
if self . get_user_by_username ( & data . username ) . await . is_ok ( ) {
2025-03-31 11:45:34 -04:00
return Err ( Error ::UsernameInUse ) ;
2025-03-30 22:33:07 -04:00
}
2025-03-22 22:17:47 -04:00
// ...
let conn = match self . connect ( ) . await {
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let res = execute! (
& conn ,
2025-04-26 16:27:18 -04:00
" INSERT INTO users VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) " ,
2025-04-03 15:07:57 -04:00
params! [
& ( data . id as i64 ) ,
& ( data . created as i64 ) ,
& data . username . to_lowercase ( ) ,
& data . password ,
& data . salt ,
& serde_json ::to_string ( & data . settings ) . unwrap ( ) ,
& serde_json ::to_string ( & data . tokens ) . unwrap ( ) ,
2025-04-03 15:56:44 -04:00
& ( FinePermission ::DEFAULT . bits ( ) as i32 ) ,
2025-04-10 18:16:52 -04:00
& ( if data . is_verified { 1_ i32 } else { 0_ i32 } ) ,
& 0_ i32 ,
& 0_ i32 ,
& 0_ i32 ,
2025-04-03 15:07:57 -04:00
& ( data . last_seen as i64 ) ,
2025-04-04 21:42:08 -04:00
& String ::new ( ) ,
2025-04-12 22:25:54 -04:00
& " [] " ,
& 0_ i32 ,
2025-04-26 16:27:18 -04:00
& 0_ i32 ,
& serde_json ::to_string ( & data . connections ) . unwrap ( ) ,
2025-03-22 22:17:47 -04:00
]
) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
Ok ( ( ) )
}
2025-03-29 00:26:56 -04:00
/// Delete an existing user in the database.
2025-03-22 22:17:47 -04:00
///
/// # Arguments
/// * `id` - the ID of the user
/// * `password` - the current password of the user
/// * `force` - if we should delete even if the given password is incorrect
2025-03-25 21:19:55 -04:00
pub async fn delete_user ( & self , id : usize , password : & str , force : bool ) -> Result < ( ) > {
2025-03-22 22:17:47 -04:00
let user = self . get_user_by_id ( id ) . await ? ;
2025-03-31 11:45:34 -04:00
if ( hash_salted ( password . to_string ( ) , user . salt . clone ( ) ) ! = user . password ) & & ! force {
2025-03-22 22:17:47 -04:00
return Err ( Error ::IncorrectPassword ) ;
}
let conn = match self . connect ( ) . await {
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
2025-04-03 13:52:29 -04:00
let res = execute! ( & conn , " DELETE FROM users WHERE id = $1 " , & [ & ( id as i64 ) ] ) ;
2025-03-22 22:17:47 -04:00
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
2025-03-31 11:45:34 -04:00
self . cache_clear_user ( & user ) . await ;
2025-03-23 21:19:16 -04:00
2025-04-01 16:12:13 -04:00
// delete communities
let res = execute! (
& conn ,
" DELETE FROM communities WHERE owner = $1 " ,
2025-04-03 13:52:29 -04:00
& [ & ( id as i64 ) ]
2025-04-01 16:12:13 -04:00
) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
// delete memberships
// member counts will remain the same... but that should probably be changed
let res = execute! (
& conn ,
" DELETE FROM memberships WHERE owner = $1 " ,
2025-04-03 13:52:29 -04:00
& [ & ( id as i64 ) ]
2025-04-01 16:12:13 -04:00
) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
// delete notifications
let res = execute! (
& conn ,
" DELETE FROM notifications WHERE owner = $1 " ,
2025-04-03 13:52:29 -04:00
& [ & ( id as i64 ) ]
2025-04-01 16:12:13 -04:00
) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
2025-04-17 21:48:45 -04:00
// delete requests
let res = execute! (
& conn ,
" DELETE FROM requests WHERE owner = $1 " ,
& [ & ( id as i64 ) ]
) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
// delete warnings
let res = execute! (
& conn ,
" DELETE FROM user_warnings WHERE receiver = $1 " ,
& [ & ( id as i64 ) ]
) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
// delete blocks
let res = execute! (
& conn ,
" DELETE FROM userblocks WHERE initiator = $1 OR receiver = $1 " ,
& [ & ( id as i64 ) ]
) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
2025-04-19 18:59:55 -04:00
let res = execute! (
& conn ,
" DELETE FROM ipblocks WHERE initiator = $1 " ,
& [ & ( id as i64 ) ]
) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
2025-04-01 16:12:13 -04:00
// delete reactions
// reactions counts will remain the same :)
let res = execute! (
& conn ,
" DELETE FROM reactions WHERE owner = $1 " ,
2025-04-03 13:52:29 -04:00
& [ & ( id as i64 ) ]
2025-04-01 16:12:13 -04:00
) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
// delete posts
2025-04-03 13:52:29 -04:00
let res = execute! ( & conn , " DELETE FROM posts WHERE owner = $1 " , & [ & ( id as i64 ) ] ) ;
2025-04-01 16:12:13 -04:00
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
2025-04-17 21:48:45 -04:00
// delete user follows... individually since it requires updating user counts
for follow in self . get_userfollows_by_receiver_all ( id ) . await ? {
self . delete_userfollow ( follow . id , & user , true ) . await ? ;
}
for follow in self . get_userfollows_by_initiator_all ( id ) . await ? {
self . delete_userfollow ( follow . id , & user , true ) . await ? ;
}
2025-04-01 16:12:13 -04:00
// remove images
let avatar = PathBufD ::current ( ) . extend ( & [
self . 0. dirs . media . as_str ( ) ,
" avatars " ,
2025-04-03 15:07:57 -04:00
& format! ( " {} .avif " , & ( user . id as i64 ) ) ,
2025-04-01 16:12:13 -04:00
] ) ;
let banner = PathBufD ::current ( ) . extend ( & [
self . 0. dirs . media . as_str ( ) ,
" banners " ,
2025-04-03 15:07:57 -04:00
& format! ( " {} .avif " , & ( user . id as i64 ) ) ,
2025-04-01 16:12:13 -04:00
] ) ;
if exists ( & avatar ) . unwrap ( ) {
remove_file ( avatar ) . unwrap ( ) ;
}
if exists ( & banner ) . unwrap ( ) {
remove_file ( banner ) . unwrap ( ) ;
}
// ...
2025-03-22 22:17:47 -04:00
Ok ( ( ) )
}
2025-03-26 21:46:21 -04:00
pub async fn update_user_verified_status ( & self , id : usize , x : bool , user : User ) -> Result < ( ) > {
if ! user . permissions . check ( FinePermission ::MANAGE_VERIFIED ) {
return Err ( Error ::NotAllowed ) ;
}
2025-04-01 16:12:13 -04:00
let other_user = self . get_user_by_id ( id ) . await ? ;
2025-03-26 21:46:21 -04:00
let conn = match self . connect ( ) . await {
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let res = execute! (
& conn ,
2025-04-01 16:12:13 -04:00
" UPDATE users SET verified = $1 WHERE id = $2 " ,
2025-04-10 18:16:52 -04:00
params! [ & { if x { 1 } else { 0 } } , & ( id as i64 ) ]
2025-03-26 21:46:21 -04:00
) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
2025-04-01 16:12:13 -04:00
self . cache_clear_user ( & other_user ) . await ;
2025-03-26 21:46:21 -04:00
2025-04-02 11:39:51 -04:00
// create audit log entry
self . create_audit_log_entry ( AuditLogEntry ::new (
user . id ,
format! (
" invoked `update_user_verified_status` with x value `{}` and y value `{}` " ,
other_user . id , x
) ,
) )
. await ? ;
// ...
2025-03-26 21:46:21 -04:00
Ok ( ( ) )
}
2025-03-31 11:45:34 -04:00
pub async fn update_user_password (
& self ,
id : usize ,
from : String ,
to : String ,
user : User ,
force : bool ,
) -> Result < ( ) > {
// verify password
if ( hash_salted ( from . clone ( ) , user . salt . clone ( ) ) ! = user . password ) & & ! force {
return Err ( Error ::MiscError ( " Password does not match " . to_string ( ) ) ) ;
}
// ...
let conn = match self . connect ( ) . await {
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let new_salt = salt ( ) ;
let new_password = hash_salted ( to , new_salt . clone ( ) ) ;
let res = execute! (
& conn ,
" UPDATE users SET password = $1, salt = $2 WHERE id = $3 " ,
2025-04-03 15:07:57 -04:00
params! [ & new_password . as_str ( ) , & new_salt . as_str ( ) , & ( id as i64 ) ]
2025-03-31 11:45:34 -04:00
) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
self . cache_clear_user ( & user ) . await ;
Ok ( ( ) )
}
pub async fn update_user_username ( & self , id : usize , to : String , user : User ) -> Result < ( ) > {
let conn = match self . connect ( ) . await {
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let res = execute! (
& conn ,
2025-04-10 18:16:52 -04:00
" UPDATE users SET username = $1 WHERE id = $2 " ,
2025-04-03 15:07:57 -04:00
params! [ & to . as_str ( ) , & ( id as i64 ) ]
2025-03-31 11:45:34 -04:00
) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
self . cache_clear_user ( & user ) . await ;
Ok ( ( ) )
}
2025-04-02 11:39:51 -04:00
pub async fn update_user_role (
& self ,
id : usize ,
role : FinePermission ,
user : User ,
) -> Result < ( ) > {
// check permission
if ! user . permissions . check ( FinePermission ::MANAGE_USERS ) {
return Err ( Error ::NotAllowed ) ;
}
let other_user = self . get_user_by_id ( id ) . await ? ;
if other_user . permissions . check_manager ( ) & & ! user . permissions . check_admin ( ) {
return Err ( Error ::MiscError (
" Cannot manage the role of other managers " . to_string ( ) ,
) ) ;
}
if other_user . permissions = = user . permissions {
return Err ( Error ::MiscError (
" Cannot manage users of equal level to you " . to_string ( ) ,
) ) ;
}
// ...
let conn = match self . connect ( ) . await {
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let res = execute! (
& conn ,
" UPDATE users SET permissions = $1 WHERE id = $2 " ,
2025-04-03 15:56:44 -04:00
params! [ & ( role . bits ( ) as i32 ) , & ( id as i64 ) ]
2025-04-02 11:39:51 -04:00
) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
self . cache_clear_user ( & other_user ) . await ;
// create audit log entry
self . create_audit_log_entry ( AuditLogEntry ::new (
user . id ,
format! (
" invoked `update_user_role` with x value `{}` and y value `{}` " ,
other_user . id ,
role . bits ( )
) ,
) )
. await ? ;
// ...
Ok ( ( ) )
}
2025-04-02 14:11:01 -04:00
pub async fn seen_user ( & self , user : & User ) -> Result < ( ) > {
let conn = match self . connect ( ) . await {
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let res = execute! (
& conn ,
" UPDATE users SET last_seen = $1 WHERE id = $2 " ,
2025-04-03 15:07:57 -04:00
params! [ & ( unix_epoch_timestamp ( ) as i64 ) , & ( user . id as i64 ) ]
2025-04-02 14:11:01 -04:00
) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
2025-04-10 18:16:52 -04:00
self . cache_clear_user ( user ) . await ;
2025-04-02 14:11:01 -04:00
Ok ( ( ) )
}
2025-04-04 21:42:08 -04:00
/// Validate a given TOTP code for the given profile.
pub fn check_totp ( & self , ua : & User , code : & str ) -> bool {
let totp = ua . totp ( Some (
self . 0
2025-04-09 00:10:58 -04:00
. host
2025-04-04 21:42:08 -04:00
. replace ( " http:// " , " " )
. replace ( " https:// " , " " )
. replace ( " : " , " _ " ) ,
) ) ;
if let Some ( totp ) = totp {
return ! code . is_empty ( )
& & ( totp . check_current ( code ) . unwrap ( )
| ua . recovery_codes . contains ( & code . to_string ( ) ) ) ;
}
true
}
/// Generate 8 random recovery codes for TOTP.
pub fn generate_totp_recovery_codes ( ) -> Vec < String > {
let mut out : Vec < String > = Vec ::new ( ) ;
for _ in 0 .. 9 {
out . push ( salt ( ) )
}
out
}
/// Update the profile's TOTP secret.
///
/// # Arguments
/// * `id` - the ID of the user
/// * `secret` - the TOTP secret
/// * `recovery` - the TOTP recovery codes
pub async fn update_user_totp (
& self ,
id : usize ,
secret : & str ,
recovery : & Vec < String > ,
) -> Result < ( ) > {
let user = self . get_user_by_id ( id ) . await ? ;
// update
let conn = match self . connect ( ) . await {
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let res = execute! (
& conn ,
" UPDATE users SET totp = $1, recovery_codes = $2 WHERE id = $3 " ,
params! [
& secret ,
& serde_json ::to_string ( recovery ) . unwrap ( ) ,
& ( id as i64 )
]
) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
self . cache_clear_user ( & user ) . await ;
Ok ( ( ) )
}
/// Enable TOTP for a profile.
///
/// # Arguments
/// * `id` - the ID of the user to enable TOTP for
/// * `user` - the user doing this
///
/// # Returns
/// `Result<(secret, qr base64)>`
pub async fn enable_totp (
& self ,
id : usize ,
user : User ,
) -> Result < ( String , String , Vec < String > ) > {
let other_user = self . get_user_by_id ( id ) . await ? ;
if other_user . id ! = user . id {
if other_user . permissions . check ( FinePermission ::MANAGE_USERS ) {
// create audit log entry
self . create_audit_log_entry ( AuditLogEntry ::new (
user . id ,
format! ( " invoked `enable_totp` with x value ` {} ` " , other_user . id , ) ,
) )
. await ? ;
} else {
return Err ( Error ::NotAllowed ) ;
}
}
let secret = totp_rs ::Secret ::default ( ) . to_string ( ) ;
let recovery = Self ::generate_totp_recovery_codes ( ) ;
self . update_user_totp ( id , & secret , & recovery ) . await ? ;
// fetch profile again (with totp information)
let other_user = self . get_user_by_id ( id ) . await ? ;
// get totp
let totp = other_user . totp ( Some (
self . 0
2025-04-09 00:10:58 -04:00
. host
2025-04-04 21:42:08 -04:00
. replace ( " http:// " , " " )
. replace ( " https:// " , " " )
. replace ( " : " , " _ " ) ,
) ) ;
if totp . is_none ( ) {
return Err ( Error ::MiscError ( " Failed to get TOTP code " . to_string ( ) ) ) ;
}
let totp = totp . unwrap ( ) ;
// generate qr
let qr = match totp . get_qr_base64 ( ) {
Ok ( q ) = > q ,
Err ( e ) = > return Err ( Error ::MiscError ( e . to_string ( ) ) ) ,
} ;
// return
2025-04-17 21:16:08 -04:00
Ok ( ( totp . get_secret_base32 ( ) , qr , recovery ) )
2025-04-04 21:42:08 -04:00
}
2025-03-31 11:45:34 -04:00
pub async fn cache_clear_user ( & self , user : & User ) {
self . 2. remove ( format! ( " atto.user: {} " , user . id ) ) . await ;
self . 2. remove ( format! ( " atto.user: {} " , user . username ) ) . await ;
}
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 ) ;
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 ) ;
2025-04-26 16:27:18 -04:00
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 ) ;
2025-03-25 18:18:33 -04:00
2025-03-31 11:45:34 -04:00
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 ) ;
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 ) ;
2025-03-25 21:19:55 -04:00
2025-03-31 11:45:34 -04:00
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 ) ;
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 ) ;
2025-03-25 21:19:55 -04:00
2025-03-31 11:45:34 -04:00
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 ) ;
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 ) ;
2025-04-12 22:25:54 -04:00
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 ) ;
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 ) ;
2025-04-12 22:52:44 -04:00
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 ) ;
2025-04-12 22:25:54 -04:00
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 ) ;
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 ) ;
2025-03-22 22:17:47 -04:00
}