2023-11-14 13:43:33 +00:00
< ? php
namespace Appwrite\Platform\Tasks ;
use Utopia\CLI\Console ;
use Utopia\Database\Database ;
use Utopia\Database\Document ;
use Utopia\Database\Helpers\ID ;
use Utopia\Database\Helpers\Permission ;
use Utopia\Database\Helpers\Role ;
use Utopia\Database\Query ;
2024-03-06 17:34:21 +00:00
use Utopia\Platform\Action ;
2024-03-07 14:29:42 +00:00
use Utopia\Http\Validator\Text ;
2023-11-14 13:43:33 +00:00
2023-11-14 14:06:58 +00:00
class PatchRecreateRepositoriesDocuments extends Action
2023-11-14 13:43:33 +00:00
{
public static function getName () : string
{
return 'patch-recreate-repositories-documents' ;
}
public function __construct ()
{
$this
-> desc ( 'Recreate missing repositories in consoleDB from projectDBs. They can be missing if you used Appwrite 1.4.10 or 1.4.11, and deleted a function.' )
-> param ( 'after' , '' , new Text ( 36 ), 'After cursor' , true )
-> param ( 'projectId' , '' , new Text ( 36 ), 'Select project to validate' , true )
-> inject ( 'dbForConsole' )
-> inject ( 'getProjectDB' )
-> callback ( fn ( $after , $projectId , $dbForConsole , $getProjectDB ) => $this -> action ( $after , $projectId , $dbForConsole , $getProjectDB ));
}
public function action ( $after , $projectId , Database $dbForConsole , callable $getProjectDB ) : void
2023-11-14 14:06:58 +00:00
{
2023-11-14 13:43:33 +00:00
Console :: info ( " Starting the patch " );
$startTime = microtime ( true );
2023-11-14 14:06:58 +00:00
if ( ! empty ( $projectId )) {
2023-11-14 16:45:02 +00:00
try {
$project = $dbForConsole -> getDocument ( 'projects' , $projectId );
$dbForProject = call_user_func ( $getProjectDB , $project );
$this -> recreateRepositories ( $dbForConsole , $dbForProject , $project );
} catch ( \Throwable $th ) {
Console :: error ( " Unexpected error occured with Project ID { $projectId } " );
Console :: error ( '[Error] Type: ' . get_class ( $th ));
Console :: error ( '[Error] Message: ' . $th -> getMessage ());
Console :: error ( '[Error] File: ' . $th -> getFile ());
Console :: error ( '[Error] Line: ' . $th -> getLine ());
}
2023-11-14 13:43:33 +00:00
} else {
$queries = [];
2023-11-14 14:06:58 +00:00
if ( ! empty ( $after )) {
2023-11-14 13:43:33 +00:00
Console :: info ( " Iterating remaining projects after project with ID { $after } " );
$project = $dbForConsole -> getDocument ( 'projects' , $after );
$queries = [ Query :: cursorAfter ( $project )];
} else {
Console :: info ( " Iterating all projects " );
}
2023-11-14 14:06:58 +00:00
$this -> foreachDocument ( $dbForConsole , 'projects' , $queries , function ( Document $project ) use ( $getProjectDB , $dbForConsole ) {
2023-11-14 16:45:02 +00:00
$projectId = $project -> getId ();
try {
$dbForProject = call_user_func ( $getProjectDB , $project );
$this -> recreateRepositories ( $dbForConsole , $dbForProject , $project );
} catch ( \Throwable $th ) {
Console :: error ( " Unexpected error occured with Project ID { $projectId } " );
Console :: error ( '[Error] Type: ' . get_class ( $th ));
Console :: error ( '[Error] Message: ' . $th -> getMessage ());
Console :: error ( '[Error] File: ' . $th -> getFile ());
Console :: error ( '[Error] Line: ' . $th -> getLine ());
}
2023-11-14 13:43:33 +00:00
});
}
$endTime = microtime ( true );
$timeTaken = $endTime - $startTime ;
$hours = ( int )( $timeTaken / 3600 );
$timeTaken -= $hours * 3600 ;
$minutes = ( int )( $timeTaken / 60 );
$timeTaken -= $minutes * 60 ;
$seconds = ( int ) $timeTaken ;
$milliseconds = ( $timeTaken - $seconds ) * 1000 ;
Console :: info ( " Recreate patch completed in $hours h, $minutes m, $seconds s, $milliseconds mis ( total $timeTaken milliseconds) " );
}
protected function foreachDocument ( Database $database , string $collection , array $queries = [], callable $callback = null ) : void
{
$limit = 1000 ;
$results = [];
$sum = $limit ;
$latestDocument = null ;
while ( $sum === $limit ) {
$newQueries = $queries ;
if ( $latestDocument != null ) {
array_unshift ( $newQueries , Query :: cursorAfter ( $latestDocument ));
}
$newQueries [] = Query :: limit ( $limit );
$results = $database -> find ( $collection , $newQueries );
if ( empty ( $results )) {
return ;
}
$sum = count ( $results );
foreach ( $results as $document ) {
if ( is_callable ( $callback )) {
$callback ( $document );
}
}
$latestDocument = $results [ array_key_last ( $results )];
}
}
public function recreateRepositories ( Database $dbForConsole , Database $dbForProject , Document $project ) : void
{
$projectId = $project -> getId ();
Console :: log ( " Running patch for project { $projectId } " );
2023-11-14 14:06:58 +00:00
$this -> foreachDocument ( $dbForProject , 'functions' , [], function ( Document $function ) use ( $dbForProject , $dbForConsole , $project ) {
2023-11-14 13:43:33 +00:00
$isConnected = ! empty ( $function -> getAttribute ( 'providerRepositoryId' , '' ));
2023-11-14 14:06:58 +00:00
if ( $isConnected ) {
2023-11-14 13:43:33 +00:00
$repository = $dbForConsole -> getDocument ( 'repositories' , $function -> getAttribute ( 'repositoryId' , '' ));
2023-11-14 14:06:58 +00:00
if ( $repository -> isEmpty ()) {
2023-11-14 13:43:33 +00:00
$projectId = $project -> getId ();
$functionId = $function -> getId ();
Console :: success ( " Recreating repositories document for project ID { $projectId } , function ID { $functionId } " );
$repository = $dbForConsole -> createDocument ( 'repositories' , new Document ([
'$id' => ID :: unique (),
'$permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
'installationId' => $function -> getAttribute ( 'installationId' , '' ),
'installationInternalId' => $function -> getAttribute ( 'installationInternalId' , '' ),
'projectId' => $project -> getId (),
'projectInternalId' => $project -> getInternalId (),
'providerRepositoryId' => $function -> getAttribute ( 'providerRepositoryId' , '' ),
'resourceId' => $function -> getId (),
'resourceInternalId' => $function -> getInternalId (),
'resourceType' => 'function' ,
'providerPullRequestIds' => []
]));
2023-11-14 14:06:58 +00:00
$function = $dbForProject -> updateDocument ( 'functions' , $function -> getId (), $function
-> setAttribute ( 'repositoryId' , $repository -> getId ())
-> setAttribute ( 'repositoryInternalId' , $repository -> getInternalId ()));
$this -> foreachDocument ( $dbForProject , 'deployments' , [
Query :: equal ( 'resourceInternalId' , [ $function -> getInternalId ()]),
Query :: equal ( 'resourceType' , [ 'functions' ])
], function ( Document $deployment ) use ( $dbForProject , $repository ) {
$dbForProject -> updateDocument ( 'deployments' , $deployment -> getId (), $deployment
-> setAttribute ( 'repositoryId' , $repository -> getId ())
-> setAttribute ( 'repositoryInternalId' , $repository -> getInternalId ()));
});
2023-11-14 13:43:33 +00:00
}
}
});
}
}