2025-07-08 13:35:23 -04:00
use crate ::{
database ::NAME_REGEX ,
model ::{
auth ::User ,
littleweb ::{ Domain , DomainData , DomainTld } ,
permissions ::{ FinePermission , SecondaryPermission } ,
Error , Result ,
} ,
2025-07-07 14:45:30 -04:00
} ;
use crate ::{ auto_method , DataManager } ;
use oiseau ::{ cache ::Cache , execute , get , params , query_row , query_rows , PostgresRow } ;
impl DataManager {
/// Get a [`Domain`] from an SQL row.
pub ( crate ) fn get_domain_from_row ( x : & PostgresRow ) -> Domain {
Domain {
id : get ! ( x ->0 ( i64 ) ) as usize ,
created : get ! ( x ->1 ( i64 ) ) as usize ,
owner : get ! ( x ->2 ( i64 ) ) as usize ,
name : get ! ( x ->3 ( String ) ) ,
tld : ( get! ( x ->4 ( String ) ) . as_str ( ) ) . into ( ) ,
data : serde_json ::from_str ( & get! ( x ->5 ( String ) ) ) . unwrap ( ) ,
}
}
auto_method! ( get_domain_by_id ( usize as i64 ) @ get_domain_from_row -> " SELECT * FROM domains WHERE id = $1 " - - name = " domain " - - returns = Domain - - cache - key - tmpl = " atto.domain:{} " ) ;
/// Get a domain given its name and TLD.
///
/// # Arguments
/// * `name`
/// * `tld`
pub async fn get_domain_by_name_tld ( & self , name : & str , tld : & DomainTld ) -> Result < Domain > {
let conn = match self . 0. connect ( ) . await {
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let res = query_row! (
& conn ,
" SELECT * FROM domains WHERE name = $1 AND tld = $2 " ,
& [ & name , & tld . to_string ( ) ] ,
| x | { Ok ( Self ::get_domain_from_row ( x ) ) }
) ;
if res . is_err ( ) {
return Err ( Error ::GeneralNotFound ( " domain " . to_string ( ) ) ) ;
}
Ok ( res . unwrap ( ) )
}
/// Get all domains by user.
///
/// # Arguments
/// * `id` - the ID of the user to fetch domains for
pub async fn get_domains_by_user ( & self , id : usize ) -> Result < Vec < Domain > > {
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 domains WHERE owner = $1 ORDER BY created DESC " ,
& [ & ( id as i64 ) ] ,
| x | { Self ::get_domain_from_row ( x ) }
) ;
if res . is_err ( ) {
return Err ( Error ::GeneralNotFound ( " domain " . to_string ( ) ) ) ;
}
Ok ( res . unwrap ( ) )
}
2025-07-08 15:33:51 -04:00
const MAXIMUM_FREE_DOMAINS : usize = 10 ;
2025-07-08 13:35:23 -04:00
2025-07-07 14:45:30 -04:00
/// Create a new domain in the database.
///
/// # Arguments
/// * `data` - a mock [`Domain`] object to insert
pub async fn create_domain ( & self , data : Domain ) -> Result < Domain > {
// check values
if data . name . len ( ) < 2 {
return Err ( Error ::DataTooShort ( " name " . to_string ( ) ) ) ;
} else if data . name . len ( ) > 128 {
return Err ( Error ::DataTooLong ( " name " . to_string ( ) ) ) ;
}
2025-07-08 13:35:23 -04:00
// check number of domains
let owner = self . get_user_by_id ( data . owner ) . await ? ;
if ! owner . permissions . check ( FinePermission ::SUPPORTER ) {
let domains = self . get_domains_by_user ( data . owner ) . await ? ;
if domains . len ( ) > = Self ::MAXIMUM_FREE_DOMAINS {
return Err ( Error ::MiscError (
" You already have the maximum number of domains you can have " . to_string ( ) ,
) ) ;
}
}
// check name
let regex = regex ::RegexBuilder ::new ( NAME_REGEX )
. multi_line ( true )
. build ( )
. unwrap ( ) ;
if regex . captures ( & data . name ) . is_some ( ) {
return Err ( Error ::MiscError (
" Domain name contains invalid characters " . to_string ( ) ,
) ) ;
}
2025-07-07 14:45:30 -04:00
// check for existing
if self
. get_domain_by_name_tld ( & data . name , & data . tld )
. await
. is_ok ( )
{
return Err ( Error ::MiscError (
" Domain + TLD already in use. Maybe try another TLD! " . 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 ,
" INSERT INTO domains VALUES ($1, $2, $3, $4, $5, $6) " ,
params! [
& ( data . id as i64 ) ,
& ( data . created as i64 ) ,
& ( data . owner as i64 ) ,
& data . name ,
& data . tld . to_string ( ) ,
& serde_json ::to_string ( & data . data ) . unwrap ( ) ,
]
) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
Ok ( data )
}
pub async fn delete_domain ( & self , id : usize , user : & User ) -> Result < ( ) > {
let domain = self . get_domain_by_id ( id ) . await ? ;
// check user permission
if user . id ! = domain . owner
& & ! user
. secondary_permissions
. check ( SecondaryPermission ::MANAGE_DOMAINS )
{
return Err ( Error ::NotAllowed ) ;
}
// ...
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 domains WHERE id = $1 " , & [ & ( id as i64 ) ] ) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
// ...
self . 0. 1. remove ( format! ( " atto.domain: {} " , id ) ) . await ;
Ok ( ( ) )
}
auto_method! ( update_domain_data ( Vec < ( String , DomainData ) > ) @ get_domain_by_id :FinePermission ::MANAGE_USERS ; -> " UPDATE domains SET data = $1 WHERE id = $2 " - - serde - - cache - key - tmpl = " atto.domain:{} " ) ;
}