2025-07-17 01:30:27 -04:00
use oiseau ::cache ::Cache ;
2025-07-17 13:34:10 -04:00
use crate ::model ::apps ::{ AppDataQuery , AppDataQueryResult , AppDataSelectMode , AppDataSelectQuery } ;
use crate ::model ::{ apps ::AppData , permissions ::FinePermission , Error , Result } ;
2025-07-17 01:30:27 -04:00
use crate ::{ auto_method , DataManager } ;
2025-07-17 13:34:10 -04:00
use oiseau ::{ PostgresRow , execute , get , query_row , query_rows , params } ;
2025-07-17 01:30:27 -04:00
2025-07-17 13:34:10 -04:00
pub const FREE_DATA_LIMIT : usize = 512_000 ;
pub const PASS_DATA_LIMIT : usize = 5_242_880 ;
2025-07-17 01:30:27 -04:00
impl DataManager {
/// Get a [`AppData`] from an SQL row.
pub ( crate ) fn get_app_data_from_row ( x : & PostgresRow ) -> AppData {
AppData {
id : get ! ( x ->0 ( i64 ) ) as usize ,
app : get ! ( x ->2 ( i64 ) ) as usize ,
key : get ! ( x ->3 ( String ) ) ,
value : get ! ( x ->4 ( String ) ) ,
}
}
auto_method! ( get_app_data_by_id ( usize as i64 ) @ get_app_data_from_row -> " SELECT * FROM app_data WHERE id = $1 " - - name = " app_data " - - returns = AppData - - cache - key - tmpl = " atto.app_data:{} " ) ;
/// Get all app_data by app.
///
/// # Arguments
/// * `id` - the ID of the app to fetch app_data for
pub async fn get_app_data_by_app ( & self , id : usize ) -> Result < Vec < AppData > > {
let conn = match self . 0. connect ( ) . await {
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let res = query_rows! (
& conn ,
" SELECT * FROM app_data WHERE app = $1 ORDER BY created DESC " ,
& [ & ( id as i64 ) ] ,
| x | { Self ::get_app_data_from_row ( x ) }
) ;
if res . is_err ( ) {
return Err ( Error ::GeneralNotFound ( " app_data " . to_string ( ) ) ) ;
}
Ok ( res . unwrap ( ) )
}
/// Get all app_data by owner.
///
/// # Arguments
/// * `id` - the ID of the user to fetch app_data for
2025-07-17 13:34:10 -04:00
pub async fn query_app_data ( & self , query : AppDataQuery ) -> Result < AppDataQueryResult > {
2025-07-17 01:30:27 -04:00
let conn = match self . 0. connect ( ) . await {
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
2025-07-17 13:34:10 -04:00
let query_str = query . to_string ( ) . replace (
" %q% " ,
& match query . query {
AppDataSelectQuery ::Like ( _ , _ ) = > format! ( " v LIKE $1 " ) ,
} ,
2025-07-17 01:30:27 -04:00
) ;
2025-07-17 13:34:10 -04:00
let res = match query . mode {
AppDataSelectMode ::One = > AppDataQueryResult ::One (
match query_row! ( & conn , & query_str , params! [ & query . query . to_string ( ) ] , | x | {
Ok ( Self ::get_app_data_from_row ( x ) )
} ) {
Ok ( x ) = > x ,
Err ( _ ) = > return Err ( Error ::GeneralNotFound ( " app_data " . to_string ( ) ) ) ,
} ,
) ,
AppDataSelectMode ::Many ( _ , _ , _ ) = > AppDataQueryResult ::Many (
match query_rows! ( & conn , & query_str , params! [ & query . query . to_string ( ) ] , | x | {
Self ::get_app_data_from_row ( x )
} ) {
Ok ( x ) = > x ,
Err ( _ ) = > return Err ( Error ::GeneralNotFound ( " app_data " . to_string ( ) ) ) ,
} ,
) ,
} ;
2025-07-17 01:30:27 -04:00
2025-07-17 13:34:10 -04:00
Ok ( res )
2025-07-17 01:30:27 -04:00
}
const MAXIMUM_FREE_APP_DATA : usize = 5 ;
const MAXIMUM_DATA_SIZE : usize = 205_000 ;
/// Create a new app_data in the database.
///
/// # Arguments
/// * `data` - a mock [`AppData`] object to insert
pub async fn create_app_data ( & self , data : AppData ) -> Result < AppData > {
let app = self . get_app_by_id ( data . app ) . await ? ;
// check values
if data . key . len ( ) < 2 {
return Err ( Error ::DataTooShort ( " key " . to_string ( ) ) ) ;
} else if data . key . len ( ) > 32 {
return Err ( Error ::DataTooLong ( " key " . to_string ( ) ) ) ;
}
if data . value . len ( ) < 2 {
return Err ( Error ::DataTooShort ( " key " . to_string ( ) ) ) ;
} else if data . value . len ( ) > Self ::MAXIMUM_DATA_SIZE {
return Err ( Error ::DataTooLong ( " key " . to_string ( ) ) ) ;
}
// check number of app_data
let owner = self . get_user_by_id ( app . owner ) . await ? ;
if ! owner . permissions . check ( FinePermission ::SUPPORTER ) {
let app_data = self
. get_table_row_count_where ( " app_data " , & format! ( " app = {} " , data . app ) )
. await ? as usize ;
if app_data > = Self ::MAXIMUM_FREE_APP_DATA {
return Err ( Error ::MiscError (
" You already have the maximum number of app_data you can have " . to_string ( ) ,
) ) ;
}
}
// ...
let conn = match self . 0. connect ( ) . await {
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let res = execute! (
& conn ,
2025-07-17 13:34:10 -04:00
" INSERT INTO app_data VALUES ($1, $2, $3, $4) " ,
2025-07-17 01:30:27 -04:00
params! [
& ( data . id as i64 ) ,
& ( data . app as i64 ) ,
& data . key ,
& data . value
]
) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
Ok ( data )
}
2025-07-17 13:34:10 -04:00
pub async fn delete_app_data ( & self , id : usize ) -> Result < ( ) > {
2025-07-17 01:30:27 -04:00
let conn = match self . 0. connect ( ) . await {
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let res = execute! ( & conn , " DELETE FROM app_data WHERE id = $1 " , & [ & ( id as i64 ) ] ) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
self . 0. 1. remove ( format! ( " atto.app_data: {} " , id ) ) . await ;
Ok ( ( ) )
}
2025-07-17 13:34:10 -04:00
auto_method! ( update_app_data_key ( & str ) -> " UPDATE app_data SET k = $1 WHERE id = $2 " - - cache - key - tmpl = " atto.app_data:{} " ) ;
auto_method! ( update_app_data_value ( & str ) -> " UPDATE app_data SET v = $1 WHERE id = $2 " - - cache - key - tmpl = " atto.app_data:{} " ) ;
2025-07-17 01:30:27 -04:00
}