2021-06-20 13:03:20 +00:00
< ? php
namespace App\Models ;
2023-11-25 10:52:05 +00:00
use Carbon\Carbon ;
2024-06-07 00:51:28 +00:00
use Illuminate\Database\Eloquent\Builder ;
2021-07-30 14:35:52 +00:00
use Illuminate\Database\Eloquent\Collection ;
2021-06-20 13:03:20 +00:00
use Illuminate\Database\Eloquent\Model ;
2024-05-09 11:22:30 +00:00
use Illuminate\Database\Eloquent\Relations\HasMany ;
2021-06-25 06:42:12 +00:00
use Illuminate\Database\Eloquent\SoftDeletes ;
2023-10-04 01:17:16 +00:00
use Illuminate\Database\QueryException ;
2021-09-06 13:39:32 +00:00
use Illuminate\Support\Facades\DB ;
2022-02-13 00:27:12 +00:00
use Illuminate\Support\Facades\Log ;
2021-06-20 13:03:20 +00:00
2023-07-17 06:36:53 +00:00
use App\Classes\FTN\ { Message , Packet };
2023-09-18 11:22:21 +00:00
use App\Exceptions\InvalidFTNException ;
2024-11-25 02:46:16 +00:00
use App\Traits\ScopeActive ;
2021-06-20 13:03:20 +00:00
2024-04-21 10:40:19 +00:00
/**
* This represents an FTN AKA .
*
* If an address is active , it belongs to the system . There can only be 1 active FTN ( z : h / n . p @ d )
* If an address is not active , it belonged to the system at a point in time in the past .
*
* If an address is validated , we know that the system is using the address ( we ' ve confirmed that during a session ) .
2024-05-09 11:22:30 +00:00
* validated is update / removed is during a mailer session , confirming if the node has the address
2024-11-04 04:58:20 +00:00
* @ todo Remove validated , if that system hasnt used the address for a defined period ( eg : 30 )
2024-04-21 10:40:19 +00:00
*
2024-05-09 11:22:30 +00:00
* We ' ll only trigger a poll to a system that we have mail for if active && validated , unless " forced " .
2024-04-21 10:40:19 +00:00
*
2024-05-09 11:22:30 +00:00
* Any mail for that address will be delivered , if active && validated .
2024-04-21 10:40:19 +00:00
*
2024-11-04 04:58:20 +00:00
* Address status :
* + Active ( active = true / validated = true ) - mail can flow and in one of our networks ( we have session details )
* + Pending ( active = true / validated = false ) - remote hasnt configured address during a session and in one of our networks
* + Known ( active = true / validated = true ) - the node presented this address , but we didnt auth it , and its a network we are not in
* + Unconfirm ( active = true / validated = true ) - the node presented this address , but we dont manage the address ( it uses a different hub )
* + Nodelist ( active = true / validated = true ) - the node presented this address , but we dont manage the address and its in a recent nodelist
* + Delisted ( active = false / validated = true ) - this node shouldnt use this address any more
* + Freed ( active = false / validated = false ) - this node shouldnt is known to not be using this address any more
*
* Other Status
* + citizen - The address belongs to one of our jurisdiction ( hub_id = our address , NC , RC , ZC )
* + foreign - The address doesnt belong to our jurisdiction
*
2024-05-09 11:22:30 +00:00
* @ see \App\Http\Requests\AddressAdd :: class for rules about AKA and role
2024-04-21 10:40:19 +00:00
*/
2024-05-09 11:22:30 +00:00
2021-06-20 13:03:20 +00:00
class Address extends Model
{
2024-11-25 02:46:16 +00:00
use ScopeActive , SoftDeletes ;
2022-01-01 05:59:35 +00:00
2023-10-04 04:49:44 +00:00
private const LOGKEY = 'MA-' ;
2022-11-01 05:39:58 +00:00
// http://ftsc.org/docs/frl-1028.002
2023-11-25 10:52:05 +00:00
public const ftn_regex = '(\d+):(\d+)/(\d+)(?:\.(\d+))?(?:@([a-zA-Z0-9\-_~]{0,8}))?' ;
2022-11-01 05:39:58 +00:00
2022-01-24 11:56:13 +00:00
public const NODE_ZC = 1 << 0 ; // Zone
public const NODE_RC = 1 << 1 ; // Region
public const NODE_NC = 1 << 2 ; // Host
public const NODE_HC = 1 << 3 ; // Hub
2024-05-09 11:22:30 +00:00
public const NODE_NN = 1 << 4 ; // Node
2024-04-21 10:40:19 +00:00
public const NODE_PVT = 1 << 5 ; // Pvt (we dont have address information) @todo
2024-09-09 10:16:14 +00:00
public const NODE_HOLD = 1 << 6 ; // Hold (user has requested hold, we havent heard from the node for 7 days
public const NODE_DOWN = 1 << 7 ; // Down we havent heard from the node for 30 days
2022-01-24 11:56:13 +00:00
public const NODE_POINT = 1 << 8 ; // Point
2024-09-09 10:16:14 +00:00
public const NODE_KEEP = 1 << 9 ; // Dont mark an address hold/down or de-list automatically
2022-01-24 11:56:13 +00:00
public const NODE_UNKNOWN = 1 << 15 ; // Unknown
2024-05-09 11:22:30 +00:00
public const NODE_ALL = 0x811f ; // Mask to catch all nodes, excluding their status
2022-01-24 11:56:13 +00:00
2024-04-12 05:29:11 +00:00
// http://ftsc.org/docs/frl-1002.001
public const ADDRESS_FIELD_MAX = 0x7fff ; // Maximum value for a field in the address
2024-05-27 11:42:03 +00:00
protected $visible = [ 'zone_id' , 'region_id' , 'host_id' , 'hub_id' , 'node_id' , 'point_id' , 'security' ];
2024-05-09 11:22:30 +00:00
/* STATIC */
2022-11-11 12:04:28 +00:00
public static function boot ()
{
parent :: boot ();
// For indexes when deleting, we need to change active to FALSE
static :: softDeleted ( function ( $model ) {
Log :: debug ( sprintf ( '%s:Deleting [%d], updating active to FALSE' , self :: LOGKEY , $model -> id ));
$model -> active = FALSE ;
$model -> save ();
});
}
2024-05-09 11:31:50 +00:00
/**
* Create an FTN address associated with a system
*
* @ param string $address
* @ param System $so
* @ return Address | null
* @ throws \Exception
*/
public static function createFTN ( string $address , System $so ) : ? self
{
$ftn = self :: parseFTN ( $address );
if ( ! $ftn [ 'd' ]) {
// See if we have a default domain for this domain
if ( $ftn [ 'z' ]) {
$zo = Zone :: where ( 'zone_id' , $ftn [ 'z' ]) -> where ( 'default' , TRUE ) -> single ();
if ( $zo )
$ftn [ 'd' ] = $zo -> domain -> name ;
else {
Log :: alert ( sprintf ( '%s:! Refusing to create address [%s] no domain available' , self :: LOGKEY , $address ));
return NULL ;
}
}
}
Log :: debug ( sprintf ( '%s:- Creating AKA [%s] for [%s]' , self :: LOGKEY , $address , $so -> name ));
// Check Domain exists
Domain :: unguard ();
2024-11-12 10:03:47 +00:00
$do = Domain :: firstOrNew ([ 'name' => $ftn [ 'd' ]]);
2024-05-09 11:31:50 +00:00
Domain :: reguard ();
if ( ! $do -> exists ) {
$do -> public = TRUE ;
$do -> active = TRUE ;
$do -> notes = 'Auto created' ;
$do -> save ();
}
// If the zone is zero, see if this is a flatten domain, and if so, find an NC
if (( $ftn [ 'z' ] === 0 ) && $do -> flatten ) {
$nc = self :: findZone ( $do , $ftn [ 'n' ], 0 , 0 );
if ( $nc ) {
Log :: info ( sprintf ( '%s:- Setting zone to [%d]' , self :: LOGKEY , $nc -> zone -> zone_id ));
$ftn [ 'z' ] = $nc -> zone -> zone_id ;
}
}
// Create zone
Zone :: unguard ();
2024-11-12 10:03:47 +00:00
$zo = Zone :: firstOrNew ([ 'domain_id' => $do -> id , 'zone_id' => $ftn [ 'z' ]]);
2024-05-09 11:31:50 +00:00
Zone :: reguard ();
if ( ! $zo -> exists ) {
$zo -> active = TRUE ;
$zo -> notes = 'Auto created' ;
$zo -> system_id = System :: createUnknownSystem () -> id ;
$do -> zones () -> save ( $zo );
}
if ( ! $zo -> active || ! $do -> active ) {
Log :: alert ( sprintf ( '%s:! Refusing to create address [%s] in disabled zone or domain' , self :: LOGKEY , $address ));
return NULL ;
}
// Create Address, assigned to $so
$o = new self ;
$o -> active = TRUE ;
$o -> zone_id = $zo -> id ;
2024-05-20 11:31:21 +00:00
$o -> region_id = $ftn [ 'r' ];
2024-05-09 11:31:50 +00:00
$o -> host_id = $ftn [ 'n' ];
$o -> node_id = $ftn [ 'f' ];
$o -> point_id = $ftn [ 'p' ];
try {
$so -> addresses () -> save ( $o );
} catch ( QueryException $e ) {
Log :: error ( sprintf ( '%s:! ERROR creating address [%s] (%s)' , self :: LOGKEY , $o -> ftn , get_class ( $e )));
return NULL ;
}
return $o ;
}
/**
* Find a record in the DB for a node string , eg : 10 : 1 / 1.0
*
* @ param string $address
* @ param bool $trashed
2024-07-11 13:33:17 +00:00
* @ param bool $recent
2024-05-09 11:31:50 +00:00
* @ return Address | null
* @ throws \Exception
*/
2024-07-11 13:33:17 +00:00
public static function findFTN ( string $address , bool $trashed = FALSE , bool $recent = FALSE ) : ? self
2024-05-09 11:31:50 +00:00
{
$ftn = self :: parseFTN ( $address );
$o = NULL ;
$query = ( new self )
-> select ( 'addresses.*' )
-> join ( 'zones' ,[ 'zones.id' => 'addresses.zone_id' ])
-> join ( 'domains' ,[ 'domains.id' => 'zones.domain_id' ])
2024-07-11 13:33:17 +00:00
-> when ( $trashed , function ( $query ) use ( $recent ) {
return $query -> withTrashed ()
-> orderBy ( 'updated_at' , 'DESC' )
-> when ( $recent , fn ( $query ) => $query -> where ( fn ( $query ) => $query
-> where ( 'deleted_at' , '>=' , Carbon :: now () -> subMonth ()) -> orWhereNull ( 'deleted_at' )));
2024-05-09 11:31:50 +00:00
}, function ( $query ) {
$query -> active ();
})
-> where ( 'zones.zone_id' , $ftn [ 'z' ])
-> where ( 'node_id' , $ftn [ 'f' ])
-> where ( 'point_id' , $ftn [ 'p' ])
-> when ( $ftn [ 'd' ], function ( $query , $domain ) {
$query -> where ( 'domains.name' , $domain );
}, function ( $query ) {
$query -> where ( 'zones.default' , TRUE );
})
-> orderBy ( 'created_at' , 'DESC' );
$q = $query -> clone ();
// Are we looking for a region address
if (( $ftn [ 'f' ] === 0 ) && ( $ftn [ 'p' ] === 0 ))
$o = $query
-> where ( 'region_id' , $ftn [ 'n' ])
-> where ( 'host_id' , $ftn [ 'n' ])
2024-10-20 21:40:47 +00:00
-> with ([ 'system:id,active,name,address,pkt_msgs,last_session,hold' ])
2024-05-09 11:31:50 +00:00
-> first ();
// Look for a normal address
if ( ! $o )
$o = $q
-> where ( function ( $q ) use ( $ftn ) {
return $q
-> where ( function ( $q ) use ( $ftn ) {
return $q
-> where ( 'region_id' , $ftn [ 'n' ])
-> where ( 'host_id' , $ftn [ 'n' ]);
})
-> orWhere ( 'host_id' , $ftn [ 'n' ]);
})
2024-10-20 21:40:47 +00:00
-> with ([ 'system:id,active,name,address,pkt_msgs,last_session,hold' ])
2024-05-09 11:31:50 +00:00
-> first ();
// Check and see if we are a flattened domain, our address might be available with a different zone.
// This occurs when we are parsing 2D addresses from SEEN-BY, but we have the zone
if ( ! $o && ( $ftn [ 'p' ] === 0 )) {
if ( $ftn [ 'd' ])
2024-06-17 09:03:48 +00:00
$do = Domain :: select ( 'flatten' ) -> where ([ 'name' => $ftn [ 'd' ]]) -> single ();
2024-05-09 11:31:50 +00:00
else {
2024-06-17 09:03:48 +00:00
$zo = Zone :: where ( 'zone_id' , $ftn [ 'z' ]) -> where ( 'default' , TRUE ) -> with ([ 'domain:id,flatten' ]) -> single ();
2024-05-09 11:31:50 +00:00
$do = $zo ? -> domain ;
}
if ( $do && $do -> flatten && (( $ftn [ 'z' ] === 0 ) || $do -> zones -> pluck ( 'zone_id' ) -> contains ( $ftn [ 'z' ])))
$o = self :: findZone ( $do , $ftn [ 'n' ], $ftn [ 'f' ], $ftn [ 'p' ], $trashed );
}
2024-11-04 04:58:20 +00:00
return ( $o && ( $trashed || $o -> system -> active )) ? $o : NULL ;
2024-05-09 11:31:50 +00:00
}
2024-05-23 05:16:47 +00:00
public static function newFTN ( string $address ) : self
{
$ftn = self :: parseFTN ( $address );
2024-05-27 12:22:38 +00:00
$do = $ftn [ 'd' ] ? Domain :: where ( 'name' , $ftn [ 'd' ]) -> single () : NULL ;
2024-05-23 05:16:47 +00:00
2024-10-23 01:06:53 +00:00
$o = new self ;
$o -> region_id = $ftn [ 'r' ];
$o -> host_id = $ftn [ 'n' ];
$o -> node_id = $ftn [ 'f' ];
$o -> point_id = $ftn [ 'p' ];
2024-10-18 00:26:06 +00:00
2024-05-27 12:22:38 +00:00
$zo = Zone :: where ( 'zone_id' , $ftn [ 'z' ])
-> when ( $do , fn ( $query ) => $query -> where ( 'domain_id' , $do -> id ))
-> single ();
2024-05-23 05:16:47 +00:00
$o -> zone_id = $zo ? -> id ;
2024-10-23 01:06:53 +00:00
if (( $ftn [ 'z' ] === 0 ) || ( ! $zo )) {
Log :: alert ( sprintf ( '%s:! newFTN was parsed an FTN [%s] with a zero zone, adding empty zone in domain' , self :: LOGKEY , $address ));
$zo = new Zone ;
$zo -> domain_id = $do ? -> id ;
}
$o -> zone () -> associate ( $zo );
2024-05-23 05:16:47 +00:00
return $o ;
}
2024-05-09 11:31:50 +00:00
/**
* This is to find an address for a domain ( like fidonet ), which is technically 2 D even though it uses multiple zones .
*
* This was implemented to identify seenby and path kludges
*
* @ param Domain $do
* @ param int $host
* @ param int $node
* @ param int $point
* @ param bool $trashed
* @ return self | null
* @ throws \Exception
*/
public static function findZone ( Domain $do , int $host , int $node , int $point , bool $trashed = FALSE ) : ? self
{
if ( ! $do -> flatten )
throw new \Exception ( sprintf ( 'Domain is not set with flatten: %d' , $do -> id ));
$zones = $do -> zones -> pluck ( 'zone_id' );
$o = ( new self )
-> select ( 'addresses.*' )
-> join ( 'zones' ,[ 'zones.id' => 'addresses.zone_id' ])
-> when ( $trashed , function ( $query ) {
$query -> withTrashed ();
}, function ( $query ) {
$query -> active ();
})
-> whereIN ( 'zones.zone_id' , $zones )
-> where ( function ( $q ) use ( $host ) {
return $q
-> where ( function ( $q ) use ( $host ) {
return $q -> where ( 'region_id' , $host )
-> where ( 'host_id' , $host );
})
-> orWhere ( 'host_id' , $host );
})
-> where ( 'node_id' , $node )
-> where ( 'point_id' , $point )
-> where ( 'zones.domain_id' , $do -> id )
-> single ();
return $o ;
}
/**
* Parse a string and split it out as an FTN array
*
* @ param string $ftn
* @ return array
* @ throws \Exception
*/
public static function parseFTN ( string $ftn ) : array
{
if ( ! preg_match ( sprintf ( '#^%s$#' , self :: ftn_regex ), strtolower ( $ftn ), $matches ))
throw new InvalidFTNException ( sprintf ( 'Invalid FTN: [%s] - regex failed' , serialize ( $ftn )));
// Check our numbers are correct.
foreach ([ 1 , 2 , 3 ] as $i )
if (( ! is_numeric ( $matches [ $i ])) || ( $matches [ $i ] > self :: ADDRESS_FIELD_MAX ))
throw new InvalidFTNException ( sprintf ( 'Invalid FTN: [%s] - zone, host, or node address invalid [%d]' , $ftn , $matches [ $i ]));
if (( ! empty ( $matches [ 4 ])) AND (( ! is_numeric ( $matches [ $i ])) || ( $matches [ 4 ] > self :: ADDRESS_FIELD_MAX )))
throw new InvalidFTNException ( sprintf ( 'Invalid FTN: [%s] - point address invalid [%d]' , $ftn , $matches [ 4 ]));
2024-05-20 11:31:21 +00:00
// Work out region
2024-10-20 10:44:56 +00:00
$region_id = NULL ;
2024-05-20 11:31:21 +00:00
$zone_id = NULL ;
2024-10-18 00:26:06 +00:00
// We can only work out region/zone if we have a domain - this is for 2D parsing
2024-05-20 11:31:21 +00:00
if ( $matches [ 5 ] ? ? NULL ) {
$o = new self ;
2024-11-25 02:46:16 +00:00
$o -> host_id = ( int ) $matches [ 2 ];
$o -> node_id = ( int ) $matches [ 3 ];
2024-05-20 11:31:21 +00:00
$o -> point_id = empty ( $matches [ 4 ]) ? 0 : ( int ) $matches [ 4 ];
2024-10-18 00:26:06 +00:00
if ( $matches [ 1 ] !== " 0 " ) {
$zo = Zone :: select ( 'zones.*' )
-> where ( 'zone_id' , $matches [ 1 ])
-> join ( 'domains' ,[ 'domains.id' => 'zones.domain_id' ])
-> where ( 'domains.name' , $matches [ 5 ])
-> single ();
// Try and find out the zone from the host_id
} else {
$zo = Zone :: select ( 'zones.*' )
-> where ( fn ( $query ) => $query -> where ( 'host_id' , $matches [ 2 ]) -> orWhere ( 'region_id' , $matches [ 2 ]))
-> join ( 'domains' ,[ 'domains.id' => 'zones.domain_id' ])
-> join ( 'addresses' ,[ 'addresses.zone_id' => 'zones.id' ])
-> where ( 'domains.name' , $matches [ 5 ])
-> first ();
}
2024-05-20 11:31:21 +00:00
$o -> zone_id = $zo ? -> id ;
$parent = $o -> parent ();
$region_id = $parent ? -> region_id ;
$zone_id = $parent ? -> zone -> zone_id ;
}
2024-05-09 11:31:50 +00:00
return [
2024-10-18 00:26:06 +00:00
'z' => ( int )( $zone_id ? : $matches [ 1 ]),
2024-06-14 05:09:04 +00:00
'r' => ( int ) $region_id ,
'n' => ( int ) $matches [ 2 ],
'f' => ( int ) $matches [ 3 ],
'p' => empty ( $matches [ 4 ]) ? 0 : ( int ) $matches [ 4 ],
'd' => $matches [ 5 ] ? ? NULL
2024-05-09 11:31:50 +00:00
];
}
2021-06-24 13:09:09 +00:00
/* SCOPES */
2024-05-09 11:22:30 +00:00
/**
* An FTN is active only if the address , zone , domain is also active
*
* @ param $query
* @ return mixed
2024-11-03 22:05:27 +00:00
* @ note zones and domains needs to be joined in the base call , or use FTN ()
2024-05-09 11:22:30 +00:00
*/
2023-07-06 05:50:46 +00:00
public function scopeActiveFTN ( $query )
2023-06-23 11:28:29 +00:00
{
2024-11-03 22:05:27 +00:00
return $query
2023-06-23 11:28:29 +00:00
-> where ( 'zones.active' , TRUE )
-> where ( 'domains.active' , TRUE )
2024-11-03 22:05:27 +00:00
-> active ();
2023-06-23 11:28:29 +00:00
}
2024-06-14 05:09:04 +00:00
/**
* Select to support returning FTN address
*
* @ param $query
* @ return void
*/
public function scopeFTN ( $query )
{
2024-06-21 04:44:28 +00:00
return $query
2024-11-04 04:58:20 +00:00
-> select ([ 'addresses.id' , 'region_id' , 'host_id' , 'node_id' , 'point_id' , 'addresses.zone_id' , 'addresses.active' , 'role' , 'security' , 'addresses.system_id' , 'addresses.active' , 'validated' , 'deleted_at' ])
2024-11-03 22:05:27 +00:00
-> join ( 'zones' ,[ 'zones.id' => 'addresses.zone_id' ])
-> join ( 'domains' ,[ 'domains.id' => 'zones.domain_id' ])
-> orderBy ( 'domains.name' )
-> orderBy ( 'region_id' )
-> orderBy ( 'host_id' )
-> orderBy ( 'node_id' )
-> orderBy ( 'point_id' )
2024-06-17 09:03:48 +00:00
-> with ([
2024-11-03 22:05:27 +00:00
'zone:zones.id,domain_id,zone_id,active' ,
'zone.domain:domains.id,name,active,public' ,
2024-06-17 09:03:48 +00:00
]);
2024-06-14 05:09:04 +00:00
}
2024-11-03 22:05:27 +00:00
/** @deprecated use FTN() */
2021-06-24 13:09:09 +00:00
public function scopeFTNOrder ( $query )
{
return $query
-> orderBy ( 'region_id' )
2021-06-25 06:42:12 +00:00
-> orderBy ( 'host_id' )
2021-06-24 13:09:09 +00:00
-> orderBy ( 'node_id' )
-> orderBy ( 'point_id' );
}
2023-09-19 07:28:25 +00:00
public function scopeFTN2DOrder ( $query )
{
return $query
-> orderBy ( 'host_id' )
-> orderBy ( 'node_id' )
-> orderBy ( 'point_id' );
}
2023-11-25 10:52:05 +00:00
/**
* Return a list of addresses and the amount of uncollected echomail
*
* @ param $query
* @ return mixed
*/
public function scopeUncollectedEchomail ( $query )
2023-12-16 12:22:23 +00:00
{
return $query
-> join ( 'echomail_seenby' ,[ 'echomail_seenby.address_id' => 'addresses.id' ])
-> join ( 'echomails' ,[ 'echomails.id' => 'echomail_seenby.echomail_id' ])
-> whereNotNull ( 'export_at' )
-> whereNull ( 'sent_at' )
-> whereNull ( 'echomails.deleted_at' )
2024-11-25 02:46:16 +00:00
-> groupBy ( 'addresses.id' );
2023-12-16 12:22:23 +00:00
}
public function scopeUncollectedEchomailTotal ( $query )
2023-11-25 10:52:05 +00:00
{
return $query
2024-05-09 11:22:30 +00:00
-> select ([
'addresses.id' ,
'zone_id' ,
'host_id' ,
'node_id' ,
'point_id' ,
'system_id' ,
2024-09-10 04:52:35 +00:00
DB :: raw ( 'count(addresses.id) as uncollected_echomail' ),
2024-05-09 11:22:30 +00:00
DB :: raw ( '0 as uncollected_netmail' ),
DB :: raw ( '0 as uncollected_files' ),
])
2023-12-16 12:22:23 +00:00
-> UncollectedEchomail ();
2023-11-25 10:52:05 +00:00
}
/**
* Return a list of addresses and the amount of uncollected files
*
* @ param $query
* @ return mixed
*/
public function scopeUncollectedFiles ( $query )
{
return $query
-> join ( 'file_seenby' ,[ 'file_seenby.address_id' => 'addresses.id' ])
-> join ( 'files' ,[ 'files.id' => 'file_seenby.file_id' ])
-> whereNotNull ( 'export_at' )
-> whereNull ( 'sent_at' )
-> whereNull ( 'files.deleted_at' )
2024-11-25 02:46:16 +00:00
-> groupBy ( 'addresses.id' );
2023-11-25 10:52:05 +00:00
}
2023-12-16 12:22:23 +00:00
public function scopeUncollectedFilesTotal ( $query )
{
return $query
2024-05-09 11:22:30 +00:00
-> select ([
'addresses.id' ,
'zone_id' ,
'host_id' ,
'node_id' ,
'point_id' ,
'system_id' ,
DB :: raw ( '0 as uncollected_echomail' ),
DB :: raw ( '0 as uncollected_netmail' ),
2024-09-10 04:52:35 +00:00
DB :: raw ( 'count(addresses.id) as uncollected_files' )
2024-05-09 11:22:30 +00:00
])
2023-12-16 12:22:23 +00:00
-> UncollectedFiles ();
}
2023-11-25 10:52:05 +00:00
public function scopeUncollectedNetmail ( $query )
{
return $query
-> join ( 'netmails' ,[ 'netmails.tftn_id' => 'addresses.id' ])
-> where ( function ( $query ) {
return $query -> whereRaw ( sprintf ( '(flags & %d) > 0' , Message :: FLAG_INTRANSIT ))
-> orWhereRaw ( sprintf ( '(flags & %d) > 0' , Message :: FLAG_LOCAL ));
})
-> whereRaw ( sprintf ( '(flags & %d) = 0' , Message :: FLAG_SENT ))
2023-12-16 12:22:23 +00:00
-> whereNull ( 'sent_pkt' )
-> whereNull ( 'sent_at' )
2023-11-25 10:52:05 +00:00
-> whereNull ( 'netmails.deleted_at' )
2024-11-25 02:46:16 +00:00
-> groupBy ( 'addresses.id' );
2023-11-25 10:52:05 +00:00
}
2023-12-16 12:22:23 +00:00
/**
* Return a list of addresses and the amount of uncollected netmail
*
* @ param $query
* @ return mixed
*/
public function scopeUncollectedNetmailTotal ( $query )
{
return $query
2024-05-09 11:22:30 +00:00
-> select ([
'addresses.id' ,
'zone_id' ,
'host_id' ,
'node_id' ,
'point_id' ,
'system_id' ,
DB :: raw ( '0 as uncollected_echomail' ),
2024-09-10 04:52:35 +00:00
DB :: raw ( 'count(addresses.id) as uncollected_netmail' ),
2024-05-09 11:22:30 +00:00
DB :: raw ( '0 as uncollected_files' )
])
2023-12-16 12:22:23 +00:00
-> UncollectedNetmail ();
}
2021-06-20 13:03:20 +00:00
/* RELATIONS */
2024-05-09 11:22:30 +00:00
public function domain ()
{
return $this -> hasOneThrough ( Domain :: class , Zone :: class , 'id' , 'id' , 'zone_id' , 'domain_id' );
}
2023-12-03 07:18:05 +00:00
public function dynamics ()
{
return $this -> hasMany ( Dynamic :: class );
}
2022-01-15 02:06:15 +00:00
/**
* Echoareas this address is subscribed to
*
* @ return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
2021-08-25 12:13:49 +00:00
public function echoareas ()
{
return $this -> belongsToMany ( Echoarea :: class )
2024-10-31 11:40:58 +00:00
-> using ( AddressEchoarea :: class )
2024-05-09 11:22:30 +00:00
-> orderBy ( 'name' )
2021-08-25 12:13:49 +00:00
-> withPivot ([ 'subscribed' ]);
}
2024-05-09 11:22:30 +00:00
public function echomail_from ()
{
2024-05-23 13:28:42 +00:00
return $this -> hasMany ( Echomail :: class , 'fftn_id' , 'id' )
-> orderBy ( 'datetime' , 'DESC' )
-> limit ( 10 );
2024-05-09 11:22:30 +00:00
}
2022-01-15 02:06:15 +00:00
/**
* Echomails that this address has seen
*
* @ return \Illuminate\Database\Eloquent\Relations\BelongsToMany
2024-05-09 11:22:30 +00:00
* @ todo Rework echomail_seenby to not have a specific seenby recorded for the fftn_id , but automatically include it when generating seenbys .
2022-01-15 02:06:15 +00:00
*/
2024-05-09 11:22:30 +00:00
public function echomail_seen ()
2022-01-15 02:06:15 +00:00
{
return $this -> belongsToMany ( Echomail :: class , 'echomail_seenby' )
2023-07-15 00:46:19 +00:00
-> withPivot ([ 'export_at' , 'sent_at' , 'sent_pkt' ]);
2022-11-01 11:24:36 +00:00
}
/**
* Files that this address has seen
*
* @ return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
2024-05-09 11:22:30 +00:00
public function file_seen () : \Illuminate\Database\Eloquent\Relations\BelongsToMany
2022-11-01 11:24:36 +00:00
{
return $this -> belongsToMany ( File :: class , 'file_seenby' )
-> withPivot ([ 'sent_at' , 'export_at' ]);
}
/**
2023-12-12 21:41:15 +00:00
* Fileareas this address is subscribed to
2022-11-01 11:24:36 +00:00
*
* @ return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
2024-05-09 11:22:30 +00:00
public function fileareas () : \Illuminate\Database\Eloquent\Relations\BelongsToMany
2022-11-01 11:24:36 +00:00
{
return $this -> belongsToMany ( Filearea :: class )
2024-05-09 11:22:30 +00:00
-> orderBy ( 'name' )
2022-11-01 11:24:36 +00:00
-> withPivot ([ 'subscribed' ]);
2022-01-15 02:06:15 +00:00
}
2024-05-09 11:22:30 +00:00
/**
* If we are a hub , if role === NODE_HC and child entries have us as their hub_id
*
* @ return HasMany
*/
public function nodes_hub () : HasMany
{
return $this -> hasMany ( Address :: class , 'hub_id' , 'id' )
2024-11-25 02:46:16 +00:00
-> select ([ 'id' , 'addresses.zone_id' , 'region_id' , 'host_id' , 'node_id' , 'point_id' , 'system_id' ])
2024-05-09 11:22:30 +00:00
-> active ()
2024-06-21 04:44:28 +00:00
-> FTNorder ()
-> with ([
'zone:zones.id,domain_id,zone_id' ,
'zone.domain:domains.id,name' ,
]);
2024-05-09 11:22:30 +00:00
}
/**
* Return the nodes that belong to this NC / RC / ZC
*
* @ return HasMany
*/
public function nodes_net () : HasMany
{
return HasMany :: noConstraints (
fn () => $this -> newHasMany (
( new self ) -> newQuery ()
-> where ( 'zone_id' , $this -> zone_id )
-> where ( 'host_id' , $this -> host_id )
-> where ( 'point_id' , 0 )
-> whereNot ( 'id' , $this -> id )
-> active ()
-> FTNorder ()
2024-06-17 09:03:48 +00:00
-> with ([ 'zone:id,domain_id,zone_id' ]),
2024-05-09 11:22:30 +00:00
$this ,
NULL ,
( $this -> role_id === self :: NODE_NC ) ? 'id' : NULL )
);
}
/**
* If we are a boss node , return our children .
*
* @ return HasMany
*/
public function nodes_point () : HasMany
{
return HasMany :: noConstraints (
fn () => $this -> newHasMany (
( new self ) -> newQuery ()
-> where ( 'zone_id' , $this -> zone_id )
-> where ( 'host_id' , $this -> host_id )
-> where ( 'node_id' , $this -> node_id )
-> where ( 'point_id' , '>' , 0 )
-> whereNot ( 'id' , $this -> id )
-> active ()
-> FTNorder ()
2024-06-17 09:03:48 +00:00
-> with ([ 'zone:id,domain_id,zone_id' ]),
2024-05-09 11:22:30 +00:00
$this ,
NULL ,
( $this -> role_id !== self :: NODE_POINT ) ? 'id' : NULL )
);
}
public function nodes_region () : HasMany
{
return HasMany :: noConstraints (
fn () => $this -> newHasMany (
( new self ) -> newQuery ()
-> where ( 'zone_id' , $this -> zone_id )
-> where ( 'region_id' , $this -> region_id )
-> whereNot ( 'id' , $this -> id )
-> active ()
-> FTNorder ()
2024-06-17 09:03:48 +00:00
-> with ([ 'zone:id,domain_id,zone_id' ]),
2024-05-09 11:22:30 +00:00
$this ,
NULL ,
( $this -> role_id === self :: NODE_RC ) ? 'id' : NULL )
);
}
public function nodes_zone () : HasMany
{
return HasMany :: noConstraints (
fn () => $this -> newHasMany (
( new self ) -> newQuery ()
-> where ( 'zone_id' , $this -> zone_id )
-> whereNot ( 'id' , $this -> id )
-> active ()
-> FTNorder ()
2024-06-17 09:03:48 +00:00
-> with ([ 'zone:id,domain_id,zone_id' ]),
2024-05-09 11:22:30 +00:00
$this ,
NULL ,
( $this -> role_id === self :: NODE_ZC ) ? 'id' : NULL )
);
}
2021-06-20 13:03:20 +00:00
public function system ()
{
return $this -> belongsTo ( System :: class );
}
2024-05-09 11:22:30 +00:00
public function uplink_hub ()
{
return $this -> belongsTo ( Address :: class , 'hub_id' , 'id' );
}
2021-06-20 13:03:20 +00:00
public function zone ()
{
return $this -> belongsTo ( Zone :: class );
}
/* ATTRIBUTES */
2023-06-23 11:28:29 +00:00
/**
* Return if this address is active
*
* @ param bool $value
* @ return bool
*/
public function getActiveAttribute ( bool $value ) : bool
{
2023-07-29 03:17:36 +00:00
return $value && $this -> getActiveDomainAttribute ();
}
public function getActiveDomainAttribute () : bool
{
return $this -> zone -> active && $this -> zone -> domain -> active ;
2023-06-23 11:28:29 +00:00
}
2021-06-20 13:03:20 +00:00
/**
* Render the node name in full 5 D
*
* @ return string
*/
2021-07-15 14:54:23 +00:00
public function getFTNAttribute () : string
2021-06-20 13:03:20 +00:00
{
2024-06-17 09:03:48 +00:00
if ( ! $this -> relationLoaded ( 'zone' ))
$this -> load ([ 'zone:id,domain_id,zone_id' , 'zone.domain:domains.id,name' ]);
2021-06-26 01:48:55 +00:00
return sprintf ( '%s@%s' , $this -> getFTN4DAttribute (), $this -> zone -> domain -> name );
}
2021-08-29 01:48:27 +00:00
public function getFTN2DAttribute () : string
{
return sprintf ( '%d/%d' , $this -> host_id ? : $this -> region_id , $this -> node_id );
}
2021-07-15 14:54:23 +00:00
public function getFTN3DAttribute () : string
2021-06-26 01:48:55 +00:00
{
2024-06-17 09:03:48 +00:00
if ( ! $this -> relationLoaded ( 'zone' ))
$this -> load ([ 'zone:id,domain_id,zone_id' ]);
2024-10-23 01:06:53 +00:00
if ( ! $this -> zone )
throw new InvalidFTNException ( sprintf ( 'Invalid Zone for FTN address [%d/%d.%d@%s]' , $this -> host_id ? : $this -> region_id , $this -> node_id , $this -> point_id , $this -> domain ? -> name ));
2023-07-17 06:36:53 +00:00
return sprintf ( '%d:%s' , $this -> zone -> zone_id , $this -> getFTN2DAttribute ());
2021-06-26 01:48:55 +00:00
}
2021-07-15 14:54:23 +00:00
public function getFTN4DAttribute () : string
2021-06-26 01:48:55 +00:00
{
2024-06-17 09:03:48 +00:00
if ( ! $this -> relationLoaded ( 'zone' ))
$this -> load ([ 'zone:id,domain_id,zone_id' ]);
2021-06-26 01:48:55 +00:00
return sprintf ( '%s.%d' , $this -> getFTN3DAttribute (), $this -> point_id );
2021-06-20 13:03:20 +00:00
}
2024-06-14 05:09:04 +00:00
public function getIsDefaultRouteAttribute () : bool
{
2024-06-18 08:31:32 +00:00
return ( ! is_null ( $x = $this -> session ( 'default' ))) && $x ;
2024-06-14 05:09:04 +00:00
}
2024-05-09 11:22:30 +00:00
public function getIsDownAttribute () : bool
{
return $this -> role & self :: NODE_DOWN ;
}
2024-06-14 05:09:04 +00:00
public function getIsHostedAttribute () : bool
{
2024-11-25 02:46:16 +00:00
return strlen ( $this -> getPassSessionAttribute () ? : '' ) > 0 ;
2024-06-14 05:09:04 +00:00
}
2024-05-09 11:22:30 +00:00
public function getIsHoldAttribute () : bool
{
return $this -> role & self :: NODE_HOLD ;
}
2024-06-15 04:23:05 +00:00
public function getIsOwnedAttribute () : bool
{
return $this -> system -> is_owned ;
}
2024-05-09 11:22:30 +00:00
public function getIsPrivateAttribute () : bool
{
2024-05-27 11:42:03 +00:00
return ( ! $this -> system -> address );
2024-05-09 11:22:30 +00:00
}
/**
* Determine this address ' role ( without status )
*
* @ return int
* @ see \App\Http\Requests\AddressAdd :: class
*/
public function getRoleIdAttribute () : int
{
2024-06-15 04:23:05 +00:00
static $warn = FALSE ;
2024-05-09 11:22:30 +00:00
$val = ( $this -> role & self :: NODE_ALL );
$role = $this -> ftn_role ();
2024-06-15 04:23:05 +00:00
if ( $this -> isRoleOverride ( $role )) {
2024-05-09 11:22:30 +00:00
if ( ! $warn ) {
$warn = TRUE ;
Log :: alert ( sprintf ( '%s:! Address ROLE [%d] is not consistent with what is expected [%d] for [%s]' , self :: LOGKEY , $val , $role , $this -> ftn ));
}
return $val ;
} else
return $role ;
}
/**
* Return a name for the role ( without status )
*
* @ return string
*/
2021-07-26 11:21:58 +00:00
public function getRoleNameAttribute () : string
2021-06-20 13:03:20 +00:00
{
2024-05-27 11:42:03 +00:00
if ( $this -> getIsDownAttribute ())
return 'DOWN' ;
if ( $this -> getIsHoldAttribute ())
return 'HOLD' ;
if ( $this -> getIsPrivateAttribute ())
return 'PVT' ;
2024-05-09 11:22:30 +00:00
switch ( $this -> role_id ) {
2022-01-24 11:56:13 +00:00
case self :: NODE_ZC :
2021-07-26 11:21:58 +00:00
return 'ZC' ;
2022-01-24 11:56:13 +00:00
case self :: NODE_RC :
2021-07-26 11:21:58 +00:00
return 'RC' ;
2022-01-24 11:56:13 +00:00
case self :: NODE_NC :
2021-07-26 11:21:58 +00:00
return 'NC' ;
2022-01-24 11:56:13 +00:00
case self :: NODE_HC :
2021-07-26 11:21:58 +00:00
return 'HUB' ;
2024-05-09 11:22:30 +00:00
case self :: NODE_NN :
2022-01-24 11:56:13 +00:00
return 'NODE' ;
case self :: NODE_POINT :
2021-07-26 11:21:58 +00:00
return 'POINT' ;
2021-06-20 13:03:20 +00:00
default :
2024-05-09 11:22:30 +00:00
return $this -> role_id ;
2021-06-20 13:03:20 +00:00
}
}
2021-06-24 10:16:37 +00:00
2024-06-14 05:09:04 +00:00
public function getPassFixAttribute () : ? string
{
return strtoupper ( $this -> session ( 'fixpass' ));
}
public function getPassPacketAttribute () : ? string
{
return strtoupper ( $this -> session ( 'pktpass' ));
}
public function getPassSessionAttribute () : ? string
{
return $this -> session ( 'sespass' );
}
public function getPassTicAttribute () : ? string
{
return strtoupper ( $this -> session ( 'ticpass' ));
}
2021-07-15 14:54:23 +00:00
/* METHODS */
2021-06-24 10:16:37 +00:00
2023-12-12 21:41:15 +00:00
/**
2024-05-09 11:31:50 +00:00
* Check the user ' s activation code for this address is correct
2024-05-09 11:22:30 +00:00
*
2024-05-09 11:31:50 +00:00
* @ param User $uo
* @ param string $code
* @ return bool
2023-12-12 21:41:15 +00:00
*/
2024-05-09 11:31:50 +00:00
public function activation_check ( User $uo , string $code ) : bool
2024-05-09 11:22:30 +00:00
{
2024-05-09 11:31:50 +00:00
try {
Log :: info ( sprintf ( '%s:Checking Activation code [%s] is valid for user [%d]' , self :: LOGKEY , $code , $uo -> id ));
2024-05-09 11:22:30 +00:00
2024-05-09 11:31:50 +00:00
return ( $code === $this -> activation_set ( $uo ));
2024-05-09 11:22:30 +00:00
2024-05-09 11:31:50 +00:00
} catch ( \Exception $e ) {
Log :: error ( sprintf ( '%s:! Activation code [%s] invalid for user [%d]' , self :: LOGKEY , $code , $uo -> id ));
return FALSE ;
}
}
/**
* Create an activation code for this address
*
* @ param User $uo
* @ return string
*/
public function activation_set ( User $uo ) : string
{
return sprintf ( '%x:%s' ,
$this -> id ,
substr ( md5 ( sprintf ( '%d:%x' , $uo -> id , timew ( $this -> updated_at ))), 0 , 10 )
);
}
/**
2024-11-25 02:46:16 +00:00
* This is the children of this record , as per normal FTN routing ZC -> RC -> NC -> HUB -> Node -> Point
*
* This a ZC would return all records for the Zone ,
* An RC would only return records in the region , etc
2024-05-09 11:31:50 +00:00
*
* @ return Collection
2024-11-25 02:46:16 +00:00
* @ see self :: parent ()
2024-05-09 11:31:50 +00:00
*/
public function children () : Collection
{
2024-05-10 23:10:00 +00:00
// If we are a point, our parent is the boss
switch ( $this -> role_id ) {
2024-11-25 02:46:16 +00:00
case self :: NODE_NN : // Normal Nodes
$o = self :: active ()
-> where ( 'zone_id' , $this -> zone_id )
-> where ( 'region_id' , $this -> region_id )
-> where ( 'host_id' , $this -> host_id )
-> where ( 'node_id' , $this -> node_id );
2024-05-10 23:10:00 +00:00
2024-11-25 02:46:16 +00:00
break ;
case self :: NODE_HC : // Hubs
$o = self :: active ()
-> where ( 'zone_id' , $this -> zone_id )
-> where ( 'region_id' , $this -> region_id )
-> where ( 'host_id' , $this -> host_id )
-> where ( 'hub_id' , $this -> id )
-> where ( 'id' , '<>' , $this -> id )
-> get ();
// Need to add in points of this hub's nodes
return $o -> merge (
self :: active ()
-> where ( 'zone_id' , $this -> zone_id )
-> where ( 'region_id' , $this -> region_id )
-> where ( 'host_id' , $this -> host_id )
-> whereIn ( 'node_id' , $o -> pluck ( 'node_id' ))
-> where ( 'point_id' , '<>' , 0 )
-> get ()
);
case self :: NODE_NC : // Nets
$o = self :: active ()
-> where ( 'zone_id' , $this -> zone_id )
-> where ( 'region_id' , $this -> region_id )
-> where ( 'host_id' , $this -> host_id );
break ;
2024-05-10 23:10:00 +00:00
2024-11-25 02:46:16 +00:00
case self :: NODE_RC : // Regions
$o = self :: active ()
-> where ( 'zone_id' , $this -> zone_id )
-> where ( 'region_id' , $this -> region_id );
break ;
case self :: NODE_ZC : // Zone
$o = self :: active ()
-> where ( 'zone_id' , $this -> zone_id );
break ;
default :
return new Collection ;
2024-05-10 23:10:00 +00:00
}
2024-05-09 11:22:30 +00:00
2024-11-25 02:46:16 +00:00
return $o
-> where ( 'id' , '<>' , $this -> id )
-> get ();
2024-05-09 11:22:30 +00:00
}
/**
2024-11-25 02:46:16 +00:00
* Contrast to children (), this takes into account authentication details , and we route mail to this
* address ( because we have session details ) and it ' s children .
2024-05-09 11:22:30 +00:00
*
* @ return Collection
* @ throws \Exception
2024-11-25 02:46:16 +00:00
* @ see self :: children ()
* @ see self :: uplink ()
2024-05-09 11:22:30 +00:00
*/
public function downlinks () : Collection
2023-12-12 21:41:15 +00:00
{
2024-11-25 02:46:16 +00:00
// We have no session data for this address, (and its not our address), by definition it has no children
2024-06-14 05:09:04 +00:00
if ( ! $this -> is_hosted && ( ! our_address () -> pluck ( 'id' ) -> contains ( $this -> id )))
2023-12-12 21:41:15 +00:00
return new Collection ;
// If this system is not marked to default route for this address
2024-06-14 05:09:04 +00:00
if ( ! $this -> is_default_route ) {
2024-11-25 02:46:16 +00:00
$children = $this -> children ()
-> push ( $this );
2023-12-12 21:41:15 +00:00
2024-05-09 11:22:30 +00:00
// We route everything for this domain
2023-12-12 21:41:15 +00:00
} else {
$children = self :: select ( 'addresses.*' )
-> join ( 'zones' ,[ 'zones.id' => 'addresses.zone_id' ])
2024-05-09 11:22:30 +00:00
-> where ( 'addresses.id' , '<>' , $this -> id )
-> where ( 'domain_id' , $this -> zone -> domain_id )
2024-05-10 14:00:58 +00:00
-> with ([ 'zone.domain' ])
2024-05-09 11:22:30 +00:00
-> active ()
-> FTNorder ()
-> get ();
2023-12-12 21:41:15 +00:00
}
// If there are no children
if ( ! $children -> count ())
return new Collection ;
// Exclude links and their children.
$exclude = collect ();
2024-11-25 02:46:16 +00:00
foreach ( our_nodes ( $this -> zone -> domain ) -> diff ([ $this ]) as $o ) {
// We only exclude downlink children
if ( $o -> role_id < $this -> role_id )
continue ;
2023-12-12 21:41:15 +00:00
// If this address is in our list, remove it and it's children
2024-11-25 02:46:16 +00:00
$exclude = $exclude -> merge ( $o -> children ());
$exclude -> push ( $o );
2023-12-12 21:41:15 +00:00
}
2024-11-25 02:46:16 +00:00
return $children -> diff ( $exclude );
2023-12-12 21:41:15 +00:00
}
2024-05-10 11:33:02 +00:00
/**
* List of all our nodes and their children
*
* @ return \Illuminate\Support\Collection
* @ throws \Exception
2024-11-25 02:46:16 +00:00
* @ deprecated use children ()
2024-05-10 11:33:02 +00:00
*/
public function downstream () : \Illuminate\Support\Collection
{
return $this -> downlinks () -> transform ( function ( $item ) {
return $item -> nodes () -> push ( $item );
}) -> flatten ();
}
2023-12-03 07:18:05 +00:00
/**
* Files waiting to be sent to this system
*
* @ return Collection
*/
public function dynamicWaiting () : Collection
{
return $this -> dynamics ()
-> where ( 'next_at' , '<=' , Carbon :: now ())
-> where ( 'active' , TRUE )
-> get ();
}
2022-01-01 05:59:35 +00:00
/**
2022-11-01 11:24:36 +00:00
* Echomail waiting to be sent to this system
2022-01-01 05:59:35 +00:00
*
2024-06-07 00:51:28 +00:00
* @ return Builder
2022-01-01 05:59:35 +00:00
*/
2024-06-17 09:03:48 +00:00
public function echomailWaiting () : Builder
2023-12-16 12:22:23 +00:00
{
2024-06-17 09:03:48 +00:00
return Echomail :: select ( 'echomails.*' )
-> join ( 'echomail_seenby' ,[ 'echomail_seenby.echomail_id' => 'echomails.id' ])
-> where ( 'address_id' , $this -> id )
-> whereNull ( 'echomails.deleted_at' )
-> whereNotNull ( 'export_at' )
-> whereNull ( 'sent_at' )
-> orderby ( 'id' )
-> with ([
'tagline:id,value' ,
'tearline:id,value' ,
'origin:id,value' ,
'echoarea:id,name,domain_id' ,
'echoarea.domain:id,name' ,
2024-11-25 02:46:16 +00:00
'fftn:id,zone_id,region_id,host_id,node_id,point_id' ,
2024-06-17 09:03:48 +00:00
'fftn.zone:id,domain_id,zone_id' ,
'fftn.zone.domain:id,name' ,
2024-11-25 02:46:16 +00:00
]);
2022-01-01 05:59:35 +00:00
}
2022-11-01 11:24:36 +00:00
/**
* Files waiting to be sent to this system
*
* @ return Collection
*/
public function filesWaiting () : Collection
{
2024-06-21 02:15:22 +00:00
return File :: select ( 'files.*' )
-> join ( 'file_seenby' ,[ 'file_seenby.file_id' => 'files.id' ])
-> where ( 'address_id' , $this -> id )
-> whereNull ( 'files.deleted_at' )
2023-07-17 06:36:53 +00:00
-> whereNotNull ( 'export_at' )
2024-06-21 02:15:22 +00:00
-> whereNull ( 'sent_at' )
-> orderby ( 'id' )
-> with ([
'filearea:id,name,domain_id' ,
'filearea.domain:id,name' ,
2024-11-25 02:46:16 +00:00
'fftn:id,zone_id,region_id,host_id,node_id,point_id' ,
2024-06-21 02:15:22 +00:00
'fftn.zone:id,domain_id,zone_id' ,
'fftn.zone.domain:id,name' ,
])
2022-11-01 11:24:36 +00:00
-> get ();
}
2024-05-09 11:22:30 +00:00
/**
* Work out what role this FTN should have
*
* @ return int | null
*/
private function ftn_role () : ? int
{
2024-05-10 23:10:00 +00:00
$role = NULL ;
2024-05-09 11:22:30 +00:00
2024-05-10 23:10:00 +00:00
// If we have a point address, we're a point
if ( $this -> point_id )
$role = self :: NODE_POINT ;
2024-05-09 11:22:30 +00:00
2024-05-10 23:10:00 +00:00
// If we have a node_id, we're either a Node or a Hub
elseif ( $this -> node_id ) {
$role = ( $this -> nodes_hub -> count ())
? self :: NODE_HC
: ((( $this -> role & Address :: NODE_ALL ) === self :: NODE_HC ) ? self :: NODE_HC : self :: NODE_NN );
2024-05-09 11:22:30 +00:00
2024-05-10 23:10:00 +00:00
// point_id and node_id are zero
// If our region_id !== host_id, and are not zero, and node_id/point_id === 0, we are an NC
} elseif (( $this -> region_id !== $this -> host_id ) && $this -> host_id ) {
$role = self :: NODE_NC ;
2024-05-09 11:22:30 +00:00
2024-05-10 23:10:00 +00:00
// point_id and node_id are zero
} elseif (( $this -> region_id === $this -> host_id ) && $this -> host_id ) {
$role = self :: NODE_RC ;
2024-05-09 11:22:30 +00:00
2024-05-10 23:10:00 +00:00
// point_id and node_id are zero
} elseif (( $this -> region_id === $this -> host_id ) && ( ! $this -> host_id )) {
$role = self :: NODE_ZC ;
}
2024-05-09 11:22:30 +00:00
2024-11-25 02:46:16 +00:00
if ( isset ( $this -> region_id ) && is_null ( $role ))
2024-05-10 23:10:00 +00:00
Log :: alert ( sprintf ( '%s:! Address ROLE [%d] could not be determined for [%s]' , self :: LOGKEY ,( $this -> role & Address :: NODE_ALL ), $this -> ftn ));
2024-05-09 11:22:30 +00:00
2024-05-10 23:10:00 +00:00
return $role ;
2024-05-09 11:22:30 +00:00
}
2021-07-30 14:35:52 +00:00
/**
* Get echomail for this node
*
* @ return Packet | null
2024-05-09 11:31:50 +00:00
* @ throws \Exception
2023-11-23 01:18:20 +00:00
* @ todo If we export to uplink hubs without our address in the seenby , they should send the message back to
* us with their seenby ' s .
2021-07-30 14:35:52 +00:00
*/
2024-05-19 13:28:45 +00:00
public function getEchomail () : ? Packet
2021-07-30 14:35:52 +00:00
{
2024-06-17 09:03:48 +00:00
if ( $count = ( $num = $this -> echomailWaiting ()) -> count ()) {
Log :: info ( sprintf ( '%s:= Got [%d] echomails for [%s] for sending' , self :: LOGKEY , $count , $this -> ftn ));
2022-12-02 13:22:56 +00:00
2024-05-19 13:28:45 +00:00
// Limit to max messages
2024-06-17 09:03:48 +00:00
if ( $count > $this -> system -> pkt_msgs )
2024-06-01 06:47:13 +00:00
Log :: notice ( sprintf ( '%s:= Only sending [%d] echomails for [%s]' , self :: LOGKEY , $this -> system -> pkt_msgs , $this -> ftn ));
2022-02-13 00:27:12 +00:00
2024-06-09 11:14:27 +00:00
return $this -> system -> packet ( $this ) -> mail ( $num -> take ( $this -> system -> pkt_msgs ) -> get ());
2021-07-30 14:35:52 +00:00
}
2024-05-19 13:28:45 +00:00
return NULL ;
2021-07-30 14:35:52 +00:00
}
2021-07-15 14:54:23 +00:00
/**
* Get netmail for this node ( including it ' s children )
2021-07-17 05:48:07 +00:00
*
* @ return Packet | null
2023-12-14 05:53:56 +00:00
* @ throws \Exception
2021-07-15 14:54:23 +00:00
*/
2024-05-19 13:28:45 +00:00
public function getNetmail () : ? Packet
2021-07-15 14:54:23 +00:00
{
2024-06-26 12:37:38 +00:00
if ( $count = ( $num = $this -> netmailAlertWaiting ()) -> count ()) {
Log :: info ( sprintf ( '%s:= Packaging [%d] netmail alerts to [%s]' , self :: LOGKEY , $count , $this -> ftn ));
2024-05-19 13:28:45 +00:00
2024-06-09 11:14:27 +00:00
$msgs = $num -> get ();
2024-05-19 13:28:45 +00:00
// Find any message that defines a packet password
2024-06-09 11:14:27 +00:00
$pass = $msgs
2024-05-19 13:28:45 +00:00
-> map ( function ( $item ) {
$passpos = strpos ( $item -> subject , ':' );
2024-06-09 11:14:27 +00:00
return (( $passpos > 0 ) && ( $passpos <= 8 )) ? substr ( $item -> subject , 0 , $passpos ) : NULL ;
2024-05-19 13:28:45 +00:00
})
-> filter ()
-> pop ();
2023-07-23 07:27:52 +00:00
2024-05-19 13:28:45 +00:00
Log :: debug ( sprintf ( '%s:= Overwriting system packet password with [%s] for [%s]' , self :: LOGKEY , $pass , $this -> ftn ));
2023-07-23 07:27:52 +00:00
2024-05-19 13:28:45 +00:00
return $this -> system -> packet ( $this , $pass ) -> mail (
2024-06-09 11:14:27 +00:00
$msgs -> filter ( fn ( $item ) => preg_match ( " /^ { $pass } :/ " , $item -> subject ))
2024-05-19 13:28:45 +00:00
-> transform ( function ( $item ) use ( $pass ) {
$item -> subject = preg_replace ( " /^ { $pass } :/ " , '' , $item -> subject );
return $item ;
})
);
2023-07-23 07:27:52 +00:00
}
2024-06-26 12:37:38 +00:00
if ( $count = ( $num = $this -> netmailWaiting ()) -> count ()) {
Log :: info ( sprintf ( '%s:= Got [%d] netmails for [%s] for sending' , self :: LOGKEY , $count , $this -> ftn ));
2023-07-15 12:10:05 +00:00
2024-05-19 13:28:45 +00:00
// Limit to max messages
2024-06-26 12:37:38 +00:00
if ( $count > $this -> system -> pkt_msgs )
2024-06-01 06:47:13 +00:00
Log :: alert ( sprintf ( '%s:= Only sending [%d] netmails for [%s]' , self :: LOGKEY , $this -> system -> pkt_msgs , $this -> ftn ));
2022-01-01 05:59:35 +00:00
2024-06-09 11:14:27 +00:00
return $this -> system -> packet ( $this ) -> mail ( $num -> take ( $this -> system -> pkt_msgs ) -> get ());
2021-07-30 14:35:52 +00:00
}
2024-05-19 13:28:45 +00:00
return NULL ;
2021-07-30 14:35:52 +00:00
}
2021-07-17 05:48:07 +00:00
2024-06-15 04:23:05 +00:00
public function isRoleOverride ( int $role = NULL ) : bool
2024-05-09 11:22:30 +00:00
{
$val = ( $this -> role & self :: NODE_ALL );
2024-06-15 04:23:05 +00:00
$role = $role ? : $this -> ftn_role ();
2024-05-09 11:22:30 +00:00
return ( $val && ( $role !== $val )) || ( ! $role );
}
2022-01-01 05:59:35 +00:00
/**
* Netmail waiting to be sent to this system
*
2024-06-07 00:51:28 +00:00
* @ return Builder
2023-12-12 21:41:15 +00:00
* @ throws \Exception
2022-01-01 05:59:35 +00:00
*/
2024-06-07 00:51:28 +00:00
public function netmailWaiting () : Builder
2022-01-01 05:59:35 +00:00
{
2024-09-08 08:57:48 +00:00
// Addresses that our downstream of this address, except anybody that has session details with us
$ours = our_nodes ( $this -> zone -> domain ) -> pluck ( 'id' );
$addresses = $this -> downstream ()
-> filter ( fn ( $item ) =>! $ours -> contains ( $item -> id ))
-> merge ( $this -> system -> match ( $this -> zone , Address :: NODE_ALL ));
2023-12-16 12:22:23 +00:00
$netmails = $this
-> UncollectedNetmail ()
-> select ( 'netmails.id' )
2024-09-08 08:57:48 +00:00
-> whereIn ( 'addresses.id' , $addresses -> pluck ( 'id' ))
2023-12-16 12:22:23 +00:00
-> groupBy ([ 'netmails.id' ])
2022-01-01 05:59:35 +00:00
-> get ();
2023-12-16 12:22:23 +00:00
2024-06-07 00:51:28 +00:00
return Netmail :: whereIn ( 'id' , $netmails -> pluck ( 'id' ));
2022-01-01 05:59:35 +00:00
}
2023-07-23 07:27:52 +00:00
/**
* Netmail alerts waiting to be sent to this system
*
2024-06-09 11:14:27 +00:00
* @ return Builder
2023-12-18 04:13:16 +00:00
* @ throws \Exception
2024-05-19 13:28:45 +00:00
* @ note The packet password to use is on the subject line for these alerts
2023-07-23 07:27:52 +00:00
*/
2024-06-09 11:14:27 +00:00
public function netmailAlertWaiting () : Builder
2023-07-23 07:27:52 +00:00
{
2023-12-16 12:22:23 +00:00
$netmails = $this
-> UncollectedNetmail ()
2023-07-23 07:27:52 +00:00
-> whereRaw ( sprintf ( '(flags & %d) > 0' , Message :: FLAG_LOCAL ))
-> whereRaw ( sprintf ( '(flags & %d) > 0' , Message :: FLAG_PKTPASSWD ))
-> whereRaw ( sprintf ( '(flags & %d) = 0' , Message :: FLAG_SENT ))
2023-12-16 12:22:23 +00:00
-> select ( 'netmails.id' )
2024-05-23 23:28:17 +00:00
-> whereIn ( 'addresses.id' , $this -> downlinks () -> add ( $this ) -> pluck ( 'id' ))
2023-12-16 12:22:23 +00:00
-> groupBy ([ 'netmails.id' ])
2023-07-23 07:27:52 +00:00
-> get ();
2023-12-16 12:22:23 +00:00
2024-06-09 11:14:27 +00:00
return Netmail :: whereIn ( 'id' , $netmails -> pluck ( 'id' ));
2023-07-23 07:27:52 +00:00
}
2024-05-10 11:33:02 +00:00
public function nodes () : Collection
{
switch ( $this -> role_id ) {
case self :: NODE_NN :
return $this -> nodes_point ;
case self :: NODE_HC :
return $this -> nodes_hub ;
case self :: NODE_NC :
return $this -> nodes_net ;
case self :: NODE_RC :
return $this -> nodes_region ;
case self :: NODE_ZC :
return $this -> nodes_zone ;
default :
return new Collection ;
}
}
2023-12-12 21:41:15 +00:00
/**
2024-11-25 02:46:16 +00:00
* Find the immediate parent for this address , as per normal FTN routing ZC <- RC <- NC <- HUB <- Node <- Point
2023-12-12 21:41:15 +00:00
*
* @ return Address | null
* @ throws \Exception
2024-11-25 02:46:16 +00:00
* @ see self :: children ()
2023-12-12 21:41:15 +00:00
*/
public function parent () : ? Address
{
2024-05-09 11:22:30 +00:00
// If we are a point, our parent is the boss
switch ( $this -> role_id ) {
case self :: NODE_POINT : // BOSS Node
2024-11-25 02:46:16 +00:00
return self :: active ()
2024-05-09 11:22:30 +00:00
-> where ( 'zone_id' , $this -> zone_id )
2024-11-25 02:46:16 +00:00
-> where ( 'region_id' , $this -> region_id )
2024-05-09 11:22:30 +00:00
-> where ( 'host_id' , $this -> host_id )
-> where ( 'node_id' , $this -> node_id )
-> where ( 'point_id' , 0 )
-> single ();
2023-12-12 21:41:15 +00:00
2024-05-09 11:22:30 +00:00
case self :: NODE_NN : // HUB if it exists, otherwise NC
if ( $this -> uplink_hub )
return $this -> uplink_hub ;
2023-12-12 21:41:15 +00:00
2024-05-09 11:22:30 +00:00
// Else fall through
2023-12-12 21:41:15 +00:00
2024-11-25 02:46:16 +00:00
case self :: NODE_HC : // NC
return self :: active ()
2024-05-09 11:22:30 +00:00
-> where ( 'zone_id' , $this -> zone_id )
2024-11-25 02:46:16 +00:00
-> where ( 'region_id' , $this -> region_id )
2024-05-09 11:22:30 +00:00
-> where ( 'host_id' , $this -> host_id )
2023-12-12 21:41:15 +00:00
-> where ( 'node_id' , 0 )
2024-05-09 11:22:30 +00:00
-> where ( 'point_id' , 0 )
2023-12-12 21:41:15 +00:00
-> single ();
2024-05-09 11:22:30 +00:00
case self :: NODE_NC : // RC
2024-11-25 02:46:16 +00:00
return self :: active ()
2024-05-09 11:22:30 +00:00
-> where ( 'zone_id' , $this -> zone_id )
2023-12-12 21:41:15 +00:00
-> where ( 'region_id' , $this -> region_id )
-> where ( 'host_id' , $this -> region_id )
-> where ( 'node_id' , 0 )
2024-05-09 11:22:30 +00:00
-> where ( 'point_id' , 0 )
2023-12-12 21:41:15 +00:00
-> single ();
2024-05-09 11:22:30 +00:00
case self :: NODE_RC : // ZC
2024-11-25 02:46:16 +00:00
return self :: active ()
2024-05-09 11:22:30 +00:00
-> where ( 'zone_id' , $this -> zone_id )
-> where ( 'region_id' , 0 )
-> where ( 'node_id' , 0 )
2023-12-12 21:41:15 +00:00
-> where ( 'point_id' , 0 )
-> single ();
default :
2024-05-09 11:22:30 +00:00
return NULL ;
2023-12-12 21:41:15 +00:00
}
}
2021-06-24 13:09:09 +00:00
/**
* Retrieve the address session details / passwords
*
* @ param string $type
* @ return string | null
*/
2024-06-14 05:09:04 +00:00
private function session ( string $type ) : ? string
2021-06-24 10:16:37 +00:00
{
2023-10-02 22:11:08 +00:00
return ( $this -> exists && ( $x = $this -> system -> sessions -> where ( 'id' , $this -> zone_id ) -> first ())) ? ( $x -> pivot -> { $type } ? : '' ) : NULL ;
2021-06-24 10:16:37 +00:00
}
2024-05-09 11:22:30 +00:00
/**
2024-11-25 02:46:16 +00:00
* Contrast to parent (), this takes into account authentication details , and we route mail to this
* address ( because we have session details ) with that uplink .
2024-05-09 11:22:30 +00:00
*
* @ return Address | $this | null
* @ throws \Exception
2024-11-25 02:46:16 +00:00
* @ see self :: parent ()
* @ see self :: downlinks ()
2024-05-09 11:22:30 +00:00
*/
public function uplink () : ? Address
{
// If it is our address
if ( our_address () -> contains ( $this ))
return NULL ;
// If we have session password, then we are the parent
2024-06-14 05:09:04 +00:00
if ( $this -> is_hosted )
2024-05-09 11:22:30 +00:00
return $this ;
2024-11-25 02:46:16 +00:00
// Traverse up our parents until we have one with session details
2024-05-09 11:22:30 +00:00
if ( $x = $this -> parent () ? -> uplink ()) {
return $x ;
2024-11-25 02:46:16 +00:00
// See if we have a node registered as the default route for this zone
2024-05-09 11:22:30 +00:00
} else {
$sz = SystemZone :: whereIn ( 'zone_id' , $this -> domain -> zones -> pluck ( 'id' ))
-> where ( 'default' , TRUE )
-> single ();
2024-11-25 02:46:16 +00:00
return $sz ? -> system -> akas -> sortBy ( 'security' ) -> last ();
2024-05-09 11:22:30 +00:00
}
}
2023-12-08 04:16:49 +00:00
}