2023-05-22 10:58:13 +00:00
< ? php
2023-06-18 11:38:37 +00:00
use Appwrite\Auth\OAuth2\Github as OAuth2Github ;
2023-05-22 10:58:13 +00:00
use Appwrite\Event\Build ;
use Appwrite\Event\Delete ;
use Appwrite\Extend\Exception ;
2025-04-14 11:56:42 +00:00
use Appwrite\Network\Validator\Redirect ;
2025-01-17 04:31:39 +00:00
use Appwrite\SDK\AuthType ;
use Appwrite\SDK\ContentType ;
use Appwrite\SDK\Method ;
use Appwrite\SDK\MethodType ;
use Appwrite\SDK\Response as SDKResponse ;
2023-05-22 10:58:13 +00:00
use Appwrite\Utopia\Database\Validator\Queries\Installations ;
2024-03-06 17:34:21 +00:00
use Appwrite\Utopia\Request ;
use Appwrite\Utopia\Response ;
2023-05-28 11:39:48 +00:00
use Appwrite\Vcs\Comment ;
2024-10-08 07:54:40 +00:00
use Utopia\App ;
2023-07-30 08:30:47 +00:00
use Utopia\Config\Config ;
2024-03-06 17:34:21 +00:00
use Utopia\Database\Database ;
2023-06-18 11:38:37 +00:00
use Utopia\Database\DateTime ;
2024-03-06 17:34:21 +00:00
use Utopia\Database\Document ;
2025-02-24 15:22:23 +00:00
use Utopia\Database\Exception\Duplicate ;
2025-04-16 12:23:39 +00:00
use Utopia\Database\Exception\Order as OrderException ;
2024-03-06 17:34:21 +00:00
use Utopia\Database\Exception\Query as QueryException ;
2023-08-06 08:51:53 +00:00
use Utopia\Database\Helpers\ID ;
use Utopia\Database\Helpers\Permission ;
use Utopia\Database\Helpers\Role ;
2023-05-22 10:58:13 +00:00
use Utopia\Database\Query ;
use Utopia\Database\Validator\Authorization ;
2024-10-17 05:41:24 +00:00
use Utopia\Database\Validator\Query\Cursor ;
2025-02-06 18:29:43 +00:00
use Utopia\Detector\Detection\Framework\Astro ;
use Utopia\Detector\Detection\Framework\Flutter ;
use Utopia\Detector\Detection\Framework\NextJs ;
use Utopia\Detector\Detection\Framework\Nuxt ;
use Utopia\Detector\Detection\Framework\Remix ;
use Utopia\Detector\Detection\Framework\SvelteKit ;
use Utopia\Detector\Detection\Packager\NPM ;
use Utopia\Detector\Detection\Packager\PNPM ;
use Utopia\Detector\Detection\Packager\Yarn ;
use Utopia\Detector\Detection\Runtime\Bun ;
use Utopia\Detector\Detection\Runtime\CPP ;
use Utopia\Detector\Detection\Runtime\Dart ;
use Utopia\Detector\Detection\Runtime\Deno ;
use Utopia\Detector\Detection\Runtime\Dotnet ;
use Utopia\Detector\Detection\Runtime\Java ;
use Utopia\Detector\Detection\Runtime\Node ;
use Utopia\Detector\Detection\Runtime\PHP ;
use Utopia\Detector\Detection\Runtime\Python ;
use Utopia\Detector\Detection\Runtime\Ruby ;
use Utopia\Detector\Detection\Runtime\Swift ;
use Utopia\Detector\Detector\Framework ;
use Utopia\Detector\Detector\Packager ;
use Utopia\Detector\Detector\Runtime ;
use Utopia\Detector\Detector\Strategy ;
2024-04-01 11:08:46 +00:00
use Utopia\System\System ;
2024-10-08 07:54:40 +00:00
use Utopia\Validator\Boolean ;
use Utopia\Validator\Text ;
2025-02-06 18:29:43 +00:00
use Utopia\Validator\WhiteList ;
2024-03-06 17:34:21 +00:00
use Utopia\VCS\Adapter\Git\GitHub ;
2023-10-13 13:20:58 +00:00
use Utopia\VCS\Exception\RepositoryNotFound ;
2023-05-22 10:58:13 +00:00
2023-06-16 09:07:47 +00:00
use function Swoole\Coroutine\batch ;
2024-12-12 10:30:26 +00:00
$createGitDeployments = function ( GitHub $github , string $providerInstallationId , array $repositories , string $providerBranch , string $providerBranchUrl , string $providerRepositoryName , string $providerRepositoryUrl , string $providerRepositoryOwner , string $providerCommitHash , string $providerCommitAuthor , string $providerCommitAuthorUrl , string $providerCommitMessage , string $providerCommitUrl , string $providerPullRequestId , bool $external , Database $dbForPlatform , Build $queueForBuilds , callable $getProjectDB , Request $request ) {
2024-10-08 07:54:40 +00:00
$errors = [];
2024-10-25 12:41:14 +00:00
foreach ( $repositories as $repository ) {
2024-03-10 20:43:22 +00:00
try {
2024-10-25 12:41:14 +00:00
$resourceType = $repository -> getAttribute ( 'resourceType' );
2023-07-28 08:27:16 +00:00
2024-10-25 12:41:14 +00:00
if ( $resourceType !== " function " && $resourceType !== " site " ) {
2024-03-10 20:53:57 +00:00
continue ;
}
2023-07-28 08:27:16 +00:00
2024-10-25 12:41:14 +00:00
$projectId = $repository -> getAttribute ( 'projectId' );
2024-12-12 10:30:26 +00:00
$project = Authorization :: skip ( fn () => $dbForPlatform -> getDocument ( 'projects' , $projectId ));
2024-03-10 20:53:57 +00:00
$dbForProject = $getProjectDB ( $project );
2024-10-25 12:41:14 +00:00
$resourceCollection = $resourceType === " function " ? 'functions' : 'sites' ;
$resourceId = $repository -> getAttribute ( 'resourceId' );
$resource = Authorization :: skip ( fn () => $dbForProject -> getDocument ( $resourceCollection , $resourceId ));
2025-05-27 01:36:23 +00:00
$resourceInternalId = $resource -> getSequence ();
2024-03-10 20:53:57 +00:00
$deploymentId = ID :: unique ();
2024-10-25 12:41:14 +00:00
$repositoryId = $repository -> getId ();
2025-05-27 01:36:23 +00:00
$repositoryInternalId = $repository -> getSequence ();
2024-10-25 12:41:14 +00:00
$providerRepositoryId = $repository -> getAttribute ( 'providerRepositoryId' );
$installationId = $repository -> getAttribute ( 'installationId' );
$installationInternalId = $repository -> getAttribute ( 'installationInternalId' );
$productionBranch = $resource -> getAttribute ( 'providerBranch' );
2024-03-10 20:53:57 +00:00
$activate = false ;
if ( $providerBranch == $productionBranch && $external === false ) {
$activate = true ;
}
2023-08-09 09:52:34 +00:00
2024-03-10 20:53:57 +00:00
$owner = $github -> getOwnerName ( $providerInstallationId ) ? ? '' ;
try {
$repositoryName = $github -> getRepositoryName ( $providerRepositoryId ) ? ? '' ;
if ( empty ( $repositoryName )) {
2024-03-10 20:43:22 +00:00
throw new Exception ( Exception :: PROVIDER_REPOSITORY_NOT_FOUND );
}
2024-03-10 20:53:57 +00:00
} catch ( RepositoryNotFound $e ) {
throw new Exception ( Exception :: PROVIDER_REPOSITORY_NOT_FOUND );
}
2023-08-09 09:52:34 +00:00
2024-03-10 20:53:57 +00:00
if ( empty ( $repositoryName )) {
throw new Exception ( Exception :: PROVIDER_REPOSITORY_NOT_FOUND );
}
$isAuthorized = ! $external ;
if ( ! $isAuthorized && ! empty ( $providerPullRequestId )) {
2024-10-25 12:41:14 +00:00
if ( \in_array ( $providerPullRequestId , $repository -> getAttribute ( 'providerPullRequestIds' , []))) {
2024-03-10 20:53:57 +00:00
$isAuthorized = true ;
2024-03-10 20:43:22 +00:00
}
2024-03-10 20:53:57 +00:00
}
$commentStatus = $isAuthorized ? 'waiting' : 'failed' ;
2025-06-18 05:36:35 +00:00
$protocol = System :: getEnv ( '_APP_OPTIONS_FORCE_HTTPS' ) === 'disabled' ? 'http' : 'https' ;
2025-06-13 10:20:53 +00:00
$hostname = System :: getEnv ( '_APP_CONSOLE_DOMAIN' , System :: getEnv ( '_APP_DOMAIN' , '' ));
2023-08-19 12:51:27 +00:00
2025-06-13 10:20:53 +00:00
$authorizeUrl = $protocol . '://' . $hostname . " /console/git/authorize-contributor?projectId= { $projectId } &installationId= { $installationId } &repositoryId= { $repositoryId } &providerPullRequestId= { $providerPullRequestId } " ;
2023-07-28 08:27:16 +00:00
2024-03-10 20:53:57 +00:00
$action = $isAuthorized ? [ 'type' => 'logs' ] : [ 'type' => 'authorize' , 'url' => $authorizeUrl ];
$latestCommentId = '' ;
2024-10-25 12:41:14 +00:00
if ( ! empty ( $providerPullRequestId ) && $resource -> getAttribute ( 'providerSilentMode' , false ) === false ) {
2024-12-12 10:30:26 +00:00
$latestComment = Authorization :: skip ( fn () => $dbForPlatform -> findOne ( 'vcsComments' , [
2024-03-10 20:53:57 +00:00
Query :: equal ( 'providerRepositoryId' , [ $providerRepositoryId ]),
Query :: equal ( 'providerPullRequestId' , [ $providerPullRequestId ]),
Query :: orderDesc ( '$createdAt' ),
]));
2024-10-31 08:13:23 +00:00
if ( ! $latestComment -> isEmpty ()) {
2024-03-10 20:53:57 +00:00
$latestCommentId = $latestComment -> getAttribute ( 'providerCommentId' , '' );
2024-10-28 14:20:36 +00:00
2024-03-10 20:53:57 +00:00
$comment = new Comment ();
$comment -> parseComment ( $github -> getComment ( $owner , $repositoryName , $latestCommentId ));
2025-03-31 16:51:33 +00:00
$comment -> addBuild ( $project , $resource , $resourceType , $commentStatus , $deploymentId , $action , '' );
2024-03-10 20:53:57 +00:00
$latestCommentId = \strval ( $github -> updateComment ( $owner , $repositoryName , $latestCommentId , $comment -> generateComment ()));
} else {
$comment = new Comment ();
2025-03-31 16:51:33 +00:00
$comment -> addBuild ( $project , $resource , $resourceType , $commentStatus , $deploymentId , $action , '' );
2024-03-10 20:53:57 +00:00
$latestCommentId = \strval ( $github -> createComment ( $owner , $repositoryName , $providerPullRequestId , $comment -> generateComment ()));
if ( ! empty ( $latestCommentId )) {
$teamId = $project -> getAttribute ( 'teamId' , '' );
2024-12-12 10:30:26 +00:00
$latestComment = Authorization :: skip ( fn () => $dbForPlatform -> createDocument ( 'vcsComments' , new Document ([
2024-03-10 20:53:57 +00:00
'$id' => ID :: unique (),
'$permissions' => [
Permission :: read ( Role :: team ( ID :: custom ( $teamId ))),
Permission :: update ( Role :: team ( ID :: custom ( $teamId ), 'owner' )),
Permission :: update ( Role :: team ( ID :: custom ( $teamId ), 'developer' )),
Permission :: delete ( Role :: team ( ID :: custom ( $teamId ), 'owner' )),
Permission :: delete ( Role :: team ( ID :: custom ( $teamId ), 'developer' )),
],
'installationInternalId' => $installationInternalId ,
'installationId' => $installationId ,
2025-05-26 05:42:11 +00:00
'projectInternalId' => $project -> getSequence (),
2024-03-10 20:53:57 +00:00
'projectId' => $project -> getId (),
'providerRepositoryId' => $providerRepositoryId ,
'providerBranch' => $providerBranch ,
'providerPullRequestId' => $providerPullRequestId ,
'providerCommentId' => $latestCommentId
])));
2023-07-28 08:27:16 +00:00
}
}
2024-03-10 20:53:57 +00:00
} elseif ( ! empty ( $providerBranch )) {
2024-12-12 10:30:26 +00:00
$latestComments = Authorization :: skip ( fn () => $dbForPlatform -> find ( 'vcsComments' , [
2024-03-10 20:53:57 +00:00
Query :: equal ( 'providerRepositoryId' , [ $providerRepositoryId ]),
Query :: equal ( 'providerBranch' , [ $providerBranch ]),
Query :: orderDesc ( '$createdAt' ),
]));
2023-07-28 08:27:16 +00:00
2024-03-10 20:53:57 +00:00
foreach ( $latestComments as $comment ) {
$latestCommentId = $comment -> getAttribute ( 'providerCommentId' , '' );
$comment = new Comment ();
$comment -> parseComment ( $github -> getComment ( $owner , $repositoryName , $latestCommentId ));
2025-03-31 16:51:33 +00:00
$comment -> addBuild ( $project , $resource , $resourceType , $commentStatus , $deploymentId , $action , '' );
2024-03-10 20:53:57 +00:00
$latestCommentId = \strval ( $github -> updateComment ( $owner , $repositoryName , $latestCommentId , $comment -> generateComment ()));
2023-08-19 12:51:27 +00:00
}
2024-03-10 20:53:57 +00:00
}
2023-07-28 08:27:16 +00:00
2024-03-10 20:53:57 +00:00
if ( ! $isAuthorized ) {
2024-10-25 12:41:14 +00:00
$resourceName = $resource -> getAttribute ( 'name' );
2024-03-10 20:53:57 +00:00
$projectName = $project -> getAttribute ( 'name' );
2024-10-25 12:41:14 +00:00
$name = " { $resourceName } ( { $projectName } ) " ;
2024-03-10 20:53:57 +00:00
$message = 'Authorization required for external contributor.' ;
2024-10-25 12:41:14 +00:00
$providerRepositoryId = $repository -> getAttribute ( 'providerRepositoryId' );
2024-03-10 20:53:57 +00:00
try {
$repositoryName = $github -> getRepositoryName ( $providerRepositoryId ) ? ? '' ;
if ( empty ( $repositoryName )) {
2023-10-27 14:08:33 +00:00
throw new Exception ( Exception :: PROVIDER_REPOSITORY_NOT_FOUND );
}
2024-03-10 20:53:57 +00:00
} catch ( RepositoryNotFound $e ) {
throw new Exception ( Exception :: PROVIDER_REPOSITORY_NOT_FOUND );
2023-10-27 14:08:33 +00:00
}
2024-03-10 20:53:57 +00:00
$owner = $github -> getOwnerName ( $providerInstallationId );
$github -> updateCommitStatus ( $repositoryName , $providerCommitHash , $owner , 'failure' , $message , $authorizeUrl , $name );
continue ;
}
2023-07-28 08:27:16 +00:00
2024-03-10 20:53:57 +00:00
if ( $external ) {
$pullRequestResponse = $github -> getPullRequest ( $owner , $repositoryName , $providerPullRequestId );
$providerRepositoryName = $pullRequestResponse [ 'head' ][ 'repo' ][ 'owner' ][ 'login' ];
$providerRepositoryOwner = $pullRequestResponse [ 'head' ][ 'repo' ][ 'name' ];
}
2023-08-11 16:52:13 +00:00
2025-03-07 19:14:11 +00:00
$commands = [];
if ( ! empty ( $resource -> getAttribute ( 'installCommand' , '' ))) {
$commands [] = $resource -> getAttribute ( 'installCommand' , '' );
}
2025-03-09 15:15:08 +00:00
if ( ! empty ( $resource -> getAttribute ( 'buildCommand' , '' ))) {
$commands [] = $resource -> getAttribute ( 'buildCommand' , '' );
}
2025-03-07 19:14:11 +00:00
if ( ! empty ( $resource -> getAttribute ( 'commands' , '' ))) {
$commands [] = $resource -> getAttribute ( 'commands' , '' );
}
2025-03-31 16:51:33 +00:00
$deployment = Authorization :: skip ( fn () => $dbForProject -> createDocument ( 'deployments' , new Document ([
2024-03-10 20:53:57 +00:00
'$id' => $deploymentId ,
'$permissions' => [
Permission :: read ( Role :: any ()),
Permission :: update ( Role :: any ()),
Permission :: delete ( Role :: any ()),
],
2024-10-25 12:41:14 +00:00
'resourceId' => $resourceId ,
'resourceInternalId' => $resourceInternalId ,
'resourceType' => $resourceCollection ,
'entrypoint' => $resource -> getAttribute ( 'entrypoint' , '' ),
2025-03-07 19:14:11 +00:00
'buildCommands' => \implode ( ' && ' , $commands ),
'buildOutput' => $resource -> getAttribute ( 'outputDirectory' , '' ),
2025-03-24 14:34:02 +00:00
'adapter' => $resource -> getAttribute ( 'adapter' , '' ),
'fallbackFile' => $resource -> getAttribute ( 'fallbackFile' , '' ),
2024-03-10 20:53:57 +00:00
'type' => 'vcs' ,
'installationId' => $installationId ,
'installationInternalId' => $installationInternalId ,
'providerRepositoryId' => $providerRepositoryId ,
'repositoryId' => $repositoryId ,
'repositoryInternalId' => $repositoryInternalId ,
'providerBranchUrl' => $providerBranchUrl ,
'providerRepositoryName' => $providerRepositoryName ,
'providerRepositoryOwner' => $providerRepositoryOwner ,
'providerRepositoryUrl' => $providerRepositoryUrl ,
'providerCommitHash' => $providerCommitHash ,
'providerCommitAuthorUrl' => $providerCommitAuthorUrl ,
'providerCommitAuthor' => $providerCommitAuthor ,
2025-03-05 08:10:15 +00:00
'providerCommitMessage' => mb_strimwidth ( $providerCommitMessage , 0 , 255 , '...' ),
2024-03-10 20:53:57 +00:00
'providerCommitUrl' => $providerCommitUrl ,
'providerCommentId' => \strval ( $latestCommentId ),
'providerBranch' => $providerBranch ,
'activate' => $activate ,
2025-03-31 16:51:33 +00:00
])));
2024-03-10 20:53:57 +00:00
2025-03-18 14:29:21 +00:00
$resource = $resource
-> setAttribute ( 'latestDeploymentId' , $deployment -> getId ())
2025-05-27 01:36:23 +00:00
-> setAttribute ( 'latestDeploymentInternalId' , $deployment -> getSequence ())
2025-03-18 14:29:21 +00:00
-> setAttribute ( 'latestDeploymentCreatedAt' , $deployment -> getCreatedAt ())
-> setAttribute ( 'latestDeploymentStatus' , $deployment -> getAttribute ( 'status' , '' ));
2025-03-31 16:51:33 +00:00
Authorization :: skip ( fn () => $dbForProject -> updateDocument ( $resource -> getCollection (), $resource -> getId (), $resource ));
2025-03-18 14:29:21 +00:00
2024-11-18 14:57:07 +00:00
if ( $resource -> getCollection () === 'sites' ) {
2025-02-15 03:38:21 +00:00
$projectId = $project -> getId ();
2025-02-24 15:22:23 +00:00
// Deployment preview
2024-11-18 14:57:07 +00:00
$sitesDomain = System :: getEnv ( '_APP_DOMAIN_SITES' , '' );
2025-02-20 13:22:30 +00:00
$domain = ID :: unique () . " . " . $sitesDomain ;
2024-11-18 14:57:07 +00:00
$ruleId = md5 ( $domain );
2025-02-20 13:22:30 +00:00
Authorization :: skip (
2025-02-04 09:51:33 +00:00
fn () => $dbForPlatform -> createDocument ( 'rules' , new Document ([
2024-11-18 14:57:07 +00:00
'$id' => $ruleId ,
'projectId' => $project -> getId (),
2025-05-27 01:36:23 +00:00
'projectInternalId' => $project -> getSequence (),
2024-11-18 14:57:07 +00:00
'domain' => $domain ,
2025-02-23 20:34:14 +00:00
'type' => 'deployment' ,
2025-03-08 18:19:05 +00:00
'trigger' => 'deployment' ,
2025-03-07 09:14:45 +00:00
'deploymentId' => $deployment -> getId (),
2025-05-27 01:36:23 +00:00
'deploymentInternalId' => $deployment -> getSequence (),
2025-03-07 09:14:45 +00:00
'deploymentResourceType' => 'site' ,
'deploymentResourceId' => $resourceId ,
'deploymentResourceInternalId' => $resourceInternalId ,
'deploymentVcsProviderBranch' => $providerBranch ,
2024-11-18 14:57:07 +00:00
'status' => 'verified' ,
'certificateId' => '' ,
2025-02-25 09:47:47 +00:00
'search' => implode ( ' ' , [ $ruleId , $domain ]),
2025-03-17 10:41:02 +00:00
'owner' => 'Appwrite' ,
'region' => $project -> getAttribute ( 'region' )
2024-11-18 14:57:07 +00:00
]))
);
2025-02-24 15:22:23 +00:00
// VCS branch preview
if ( ! empty ( $providerBranch )) {
2025-05-28 07:20:47 +00:00
$branchPrefix = substr ( $providerBranch , 0 , 16 );
if ( strlen ( $providerBranch ) > 16 ) {
$remainingChars = substr ( $providerBranch , 16 );
2025-05-28 08:31:55 +00:00
$branchPrefix .= '-' . substr ( hash ( 'sha256' , $remainingChars ), 0 , 7 );
2025-05-28 07:20:47 +00:00
}
$resourceProjectHash = substr ( hash ( 'sha256' , $resource -> getId () . $project -> getId ()), 0 , 7 );
2025-05-28 07:25:50 +00:00
$domain = " branch- { $branchPrefix } - { $resourceProjectHash } . { $sitesDomain } " ;
2025-02-24 15:22:23 +00:00
$ruleId = md5 ( $domain );
try {
Authorization :: skip (
fn () => $dbForPlatform -> createDocument ( 'rules' , new Document ([
'$id' => $ruleId ,
'projectId' => $project -> getId (),
2025-05-27 01:36:23 +00:00
'projectInternalId' => $project -> getSequence (),
2025-02-24 15:22:23 +00:00
'domain' => $domain ,
'type' => 'deployment' ,
2025-03-08 18:19:05 +00:00
'trigger' => 'deployment' ,
2025-03-07 09:14:45 +00:00
'deploymentId' => $deployment -> getId (),
2025-05-27 01:36:23 +00:00
'deploymentInternalId' => $deployment -> getSequence (),
2025-03-07 09:14:45 +00:00
'deploymentResourceType' => 'site' ,
'deploymentResourceId' => $resourceId ,
'deploymentResourceInternalId' => $resourceInternalId ,
'deploymentVcsProviderBranch' => $providerBranch ,
2025-02-24 15:22:23 +00:00
'status' => 'verified' ,
'certificateId' => '' ,
2025-02-25 09:47:47 +00:00
'search' => implode ( ' ' , [ $ruleId , $domain ]),
2025-03-17 10:41:02 +00:00
'owner' => 'Appwrite' ,
'region' => $project -> getAttribute ( 'region' )
2025-02-24 15:22:23 +00:00
]))
);
} catch ( Duplicate $err ) {
// Ignore, rule already exists; will be updated by builds worker
}
}
2024-03-10 20:53:57 +00:00
2025-02-24 15:22:23 +00:00
// VCS commit preview
if ( ! empty ( $providerCommitHash )) {
2025-05-28 08:40:12 +00:00
$domain = " commit- " . substr ( $providerCommitHash , 0 , 16 ) . " . { $sitesDomain } " ;
2025-02-24 15:22:23 +00:00
$ruleId = md5 ( $domain );
try {
Authorization :: skip (
fn () => $dbForPlatform -> createDocument ( 'rules' , new Document ([
'$id' => $ruleId ,
'projectId' => $project -> getId (),
2025-05-27 01:36:23 +00:00
'projectInternalId' => $project -> getSequence (),
2025-02-24 15:22:23 +00:00
'domain' => $domain ,
'type' => 'deployment' ,
2025-03-08 18:19:05 +00:00
'trigger' => 'deployment' ,
2025-03-07 09:14:45 +00:00
'deploymentId' => $deployment -> getId (),
2025-05-27 01:36:23 +00:00
'deploymentInternalId' => $deployment -> getSequence (),
2025-03-07 09:14:45 +00:00
'deploymentResourceType' => 'site' ,
'deploymentResourceId' => $resourceId ,
'deploymentResourceInternalId' => $resourceInternalId ,
'deploymentVcsProviderBranch' => $providerBranch ,
2025-02-24 15:22:23 +00:00
'status' => 'verified' ,
'certificateId' => '' ,
2025-02-25 09:47:47 +00:00
'search' => implode ( ' ' , [ $ruleId , $domain ]),
2025-03-17 10:41:02 +00:00
'owner' => 'Appwrite' ,
'region' => $project -> getAttribute ( 'region' )
2025-02-24 15:22:23 +00:00
]))
);
} catch ( Duplicate $err ) {
// Ignore, rule already exists; will be updated by builds worker
}
}
2024-11-18 14:57:07 +00:00
}
2024-03-10 20:53:57 +00:00
2024-10-25 12:41:14 +00:00
if ( ! empty ( $providerCommitHash ) && $resource -> getAttribute ( 'providerSilentMode' , false ) === false ) {
$resourceName = $resource -> getAttribute ( 'name' );
2024-03-10 20:53:57 +00:00
$projectName = $project -> getAttribute ( 'name' );
2025-06-12 06:13:11 +00:00
$region = $project -> getAttribute ( 'region' , 'default' );
2024-10-25 12:41:14 +00:00
$name = " { $resourceName } ( { $projectName } ) " ;
2024-03-10 20:53:57 +00:00
$message = 'Starting...' ;
2023-07-28 08:27:16 +00:00
2024-10-25 12:41:14 +00:00
$providerRepositoryId = $repository -> getAttribute ( 'providerRepositoryId' );
2024-03-10 20:53:57 +00:00
try {
$repositoryName = $github -> getRepositoryName ( $providerRepositoryId ) ? ? '' ;
if ( empty ( $repositoryName )) {
2023-10-27 14:08:33 +00:00
throw new Exception ( Exception :: PROVIDER_REPOSITORY_NOT_FOUND );
}
2024-03-10 20:53:57 +00:00
} catch ( RepositoryNotFound $e ) {
throw new Exception ( Exception :: PROVIDER_REPOSITORY_NOT_FOUND );
2024-03-10 20:43:22 +00:00
}
2024-03-10 20:53:57 +00:00
$owner = $github -> getOwnerName ( $providerInstallationId );
2023-07-28 08:27:16 +00:00
2025-06-13 10:20:53 +00:00
$providerTargetUrl = $protocol . '://' . $hostname . " /console/project- $region - $projectId / $resourceCollection / $resourceType - $resourceId " ;
2024-03-10 20:53:57 +00:00
$github -> updateCommitStatus ( $repositoryName , $providerCommitHash , $owner , 'pending' , $message , $providerTargetUrl , $name );
}
2024-03-10 20:33:57 +00:00
2024-03-10 20:53:57 +00:00
$queueForBuilds
-> setType ( BUILD_TYPE_DEPLOYMENT )
2024-10-25 12:41:14 +00:00
-> setResource ( $resource )
2024-03-10 20:53:57 +00:00
-> setDeployment ( $deployment )
-> setProject ( $project ); // set the project because it won't be set for git deployments
2023-07-28 08:27:16 +00:00
2024-10-25 12:41:14 +00:00
$queueForBuilds -> trigger (); // must trigger here so that we create a build for each function/site
2024-03-10 20:53:57 +00:00
//TODO: Add event?
2024-03-10 20:43:22 +00:00
} catch ( Throwable $e ) {
$errors [] = $e -> getMessage ();
2023-07-28 08:27:16 +00:00
}
}
2024-03-10 20:33:57 +00:00
2024-03-11 10:17:40 +00:00
$queueForBuilds -> reset (); // prevent shutdown hook from triggering again
2024-03-10 20:43:22 +00:00
if ( ! empty ( $errors )) {
throw new Exception ( Exception :: GENERAL_UNKNOWN , \implode ( " \n " , $errors ));
}
2023-07-28 08:27:16 +00:00
};
2024-10-08 07:54:40 +00:00
App :: get ( '/v1/vcs/github/authorize' )
2025-04-14 18:40:48 +00:00
-> desc ( 'Create GitHub app installation' )
2023-05-22 10:58:13 +00:00
-> groups ([ 'api' , 'vcs' ])
2023-08-30 18:44:33 +00:00
-> label ( 'scope' , 'vcs.read' )
2023-09-05 10:14:43 +00:00
-> label ( 'error' , __DIR__ . '/../../views/general/error.phtml' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'vcs' ,
2025-04-12 06:50:02 +00:00
group : 'installations' ,
2025-01-17 04:31:39 +00:00
name : 'createGitHubInstallation' ,
2025-01-17 07:44:25 +00:00
description : '/docs/references/vcs/create-github-installation.md' ,
2025-01-17 04:31:39 +00:00
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_MOVED_PERMANENTLY ,
model : Response :: MODEL_NONE ,
)
],
contentType : ContentType :: HTML ,
type : MethodType :: WEBAUTH ,
hide : true ,
))
2025-04-14 11:56:42 +00:00
-> param ( 'success' , '' , fn ( $platforms ) => new Redirect ( $platforms ), 'URL to redirect back to console after a successful installation attempt.' , true , [ 'platforms' ])
-> param ( 'failure' , '' , fn ( $platforms ) => new Redirect ( $platforms ), 'URL to redirect back to console after a failed installation attempt.' , true , [ 'platforms' ])
2023-08-07 15:37:36 +00:00
-> inject ( 'request' )
2023-05-22 10:58:13 +00:00
-> inject ( 'response' )
2023-08-30 18:44:33 +00:00
-> inject ( 'project' )
-> action ( function ( string $success , string $failure , Request $request , Response $response , Document $project ) {
2023-05-25 22:29:08 +00:00
$state = \json_encode ([
2023-08-30 18:44:33 +00:00
'projectId' => $project -> getId (),
2023-08-05 18:53:42 +00:00
'success' => $success ,
'failure' => $failure ,
2023-05-25 22:29:08 +00:00
]);
2024-04-01 11:02:47 +00:00
$appName = System :: getEnv ( '_APP_VCS_GITHUB_APP_NAME' );
2025-06-18 05:36:35 +00:00
$protocol = System :: getEnv ( '_APP_OPTIONS_FORCE_HTTPS' ) === 'disabled' ? 'http' : 'https' ;
2025-06-13 10:20:53 +00:00
$hostname = System :: getEnv ( '_APP_CONSOLE_DOMAIN' , System :: getEnv ( '_APP_DOMAIN' , '' ));
2023-09-05 10:14:43 +00:00
2023-09-05 11:16:20 +00:00
if ( empty ( $appName )) {
2023-09-05 10:14:43 +00:00
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'GitHub App name is not configured. Please configure VCS (Version Control System) variables in .env file.' );
}
2023-07-29 16:03:18 +00:00
$url = " https://github.com/apps/ $appName /installations/new? " . \http_build_query ([
2023-08-07 15:37:36 +00:00
'state' => $state ,
2025-06-13 10:20:53 +00:00
'redirect_uri' => $protocol . '://' . $hostname . " /v1/vcs/github/callback "
2023-07-29 16:03:18 +00:00
]);
2023-05-22 10:58:13 +00:00
$response
-> addHeader ( 'Cache-Control' , 'no-store, no-cache, must-revalidate, max-age=0' )
-> addHeader ( 'Pragma' , 'no-cache' )
2023-07-29 16:03:18 +00:00
-> redirect ( $url );
2023-05-22 10:58:13 +00:00
});
2024-10-08 07:54:40 +00:00
App :: get ( '/v1/vcs/github/callback' )
2025-04-14 18:40:48 +00:00
-> desc ( 'Get installation and authorization from GitHub app' )
2023-05-22 10:58:13 +00:00
-> groups ([ 'api' , 'vcs' ])
2023-08-30 18:44:33 +00:00
-> label ( 'scope' , 'public' )
2023-06-22 10:59:41 +00:00
-> label ( 'error' , __DIR__ . '/../../views/general/error.phtml' )
2023-08-06 10:30:38 +00:00
-> param ( 'installation_id' , '' , new Text ( 256 , 0 ), 'GitHub installation ID' , true )
2023-10-12 20:15:27 +00:00
-> param ( 'setup_action' , '' , new Text ( 256 , 0 ), 'GitHub setup action type' , true )
2023-07-29 16:03:18 +00:00
-> param ( 'state' , '' , new Text ( 2048 ), 'GitHub state. Contains info sent when starting authorization flow.' , true )
2023-08-22 20:11:33 +00:00
-> param ( 'code' , '' , new Text ( 2048 , 0 ), 'OAuth2 code. This is a temporary code that the will be later exchanged for an access token.' , true )
2023-05-26 08:44:08 +00:00
-> inject ( 'gitHub' )
2023-06-18 11:38:37 +00:00
-> inject ( 'user' )
2023-06-08 15:24:27 +00:00
-> inject ( 'project' )
2023-05-22 10:58:13 +00:00
-> inject ( 'request' )
-> inject ( 'response' )
2024-12-12 10:30:26 +00:00
-> inject ( 'dbForPlatform' )
-> action ( function ( string $providerInstallationId , string $setupAction , string $state , string $code , GitHub $github , Document $user , Document $project , Request $request , Response $response , Database $dbForPlatform ) {
2023-08-21 13:16:02 +00:00
if ( empty ( $state )) {
$error = 'Installation requests from organisation members for the Appwrite GitHub App are currently unsupported. To proceed with the installation, login to the Appwrite Console and install the GitHub App.' ;
throw new Exception ( Exception :: GENERAL_ARGUMENT_INVALID , $error );
}
2023-05-25 22:29:08 +00:00
$state = \json_decode ( $state , true );
2023-06-18 11:38:37 +00:00
$projectId = $state [ 'projectId' ] ? ? '' ;
2023-05-25 22:29:08 +00:00
2024-12-12 10:30:26 +00:00
$project = $dbForPlatform -> getDocument ( 'projects' , $projectId );
2023-08-05 18:53:42 +00:00
2023-05-22 10:58:13 +00:00
if ( $project -> isEmpty ()) {
2023-08-05 18:53:42 +00:00
$error = 'Project with the ID from state could not be found.' ;
if ( ! empty ( $redirectFailure )) {
$separator = \str_contains ( $redirectFailure , '?' ) ? '&' : ':' ;
return $response
-> addHeader ( 'Cache-Control' , 'no-store, no-cache, must-revalidate, max-age=0' )
-> addHeader ( 'Pragma' , 'no-cache' )
-> redirect ( $redirectFailure . $separator . \http_build_query ([ 'error' => $error ]));
}
throw new Exception ( Exception :: PROJECT_NOT_FOUND , $error );
2023-05-22 10:58:13 +00:00
}
2025-06-12 06:13:11 +00:00
$region = $project -> getAttribute ( 'region' , 'default' );
2025-06-18 05:36:35 +00:00
$protocol = System :: getEnv ( '_APP_OPTIONS_FORCE_HTTPS' ) === 'disabled' ? 'http' : 'https' ;
2025-06-13 10:20:53 +00:00
$hostname = System :: getEnv ( '_APP_CONSOLE_DOMAIN' , System :: getEnv ( '_APP_DOMAIN' , '' ));
2025-06-12 06:13:11 +00:00
$defaultState = [
2025-06-13 10:20:53 +00:00
'success' => $protocol . '://' . $hostname . " /console/project- $region - $projectId /settings/git-installations " ,
'failure' => $protocol . '://' . $hostname . " /console/project- $region - $projectId /settings/git-installations " ,
2025-06-12 06:13:11 +00:00
];
$state = \array_merge ( $defaultState , $state ? ? []);
$redirectSuccess = $state [ 'success' ] ? ? '' ;
$redirectFailure = $state [ 'failure' ] ? ? '' ;
2023-06-18 11:38:37 +00:00
// Create / Update installation
2023-07-30 09:51:13 +00:00
if ( ! empty ( $providerInstallationId )) {
2024-04-01 11:02:47 +00:00
$privateKey = System :: getEnv ( '_APP_VCS_GITHUB_PRIVATE_KEY' );
$githubAppId = System :: getEnv ( '_APP_VCS_GITHUB_APP_ID' );
2023-08-09 17:35:23 +00:00
$github -> initializeVariables ( $providerInstallationId , $privateKey , $githubAppId );
2023-07-30 09:51:13 +00:00
$owner = $github -> getOwnerName ( $providerInstallationId ) ? ? '' ;
2023-06-18 11:38:37 +00:00
2025-05-26 05:42:11 +00:00
$projectInternalId = $project -> getSequence ();
2023-06-18 11:38:37 +00:00
2024-12-12 10:30:26 +00:00
$installation = $dbForPlatform -> findOne ( 'installations' , [
2023-07-30 09:51:13 +00:00
Query :: equal ( 'providerInstallationId' , [ $providerInstallationId ]),
2023-06-18 11:38:37 +00:00
Query :: equal ( 'projectInternalId' , [ $projectInternalId ])
2023-05-22 10:58:13 +00:00
]);
2024-12-27 12:13:14 +00:00
$personal = false ;
$refreshToken = null ;
$accessToken = null ;
$accessTokenExpiry = null ;
if ( ! empty ( $code )) {
$oauth2 = new OAuth2Github ( System :: getEnv ( '_APP_VCS_GITHUB_CLIENT_ID' , '' ), System :: getEnv ( '_APP_VCS_GITHUB_CLIENT_SECRET' , '' ), " " );
2024-12-27 12:32:22 +00:00
2024-12-27 12:13:14 +00:00
$accessToken = $oauth2 -> getAccessToken ( $code ) ? ? '' ;
$refreshToken = $oauth2 -> getRefreshToken ( $code ) ? ? '' ;
2024-12-27 12:32:22 +00:00
$accessTokenExpiry = DateTime :: addSeconds ( new \DateTime (), \intval ( $oauth2 -> getAccessTokenExpiry ( $code )));
2024-12-27 12:13:14 +00:00
$personalSlug = $oauth2 -> getUserSlug ( $accessToken ) ? ? '' ;
$personal = $personalSlug === $owner ;
}
2024-10-31 08:13:23 +00:00
if ( $installation -> isEmpty ()) {
2023-06-18 11:38:37 +00:00
$teamId = $project -> getAttribute ( 'teamId' , '' );
2023-07-30 09:51:13 +00:00
$installation = new Document ([
2023-06-18 11:38:37 +00:00
'$id' => ID :: unique (),
'$permissions' => [
Permission :: read ( Role :: team ( ID :: custom ( $teamId ))),
Permission :: update ( Role :: team ( ID :: custom ( $teamId ), 'owner' )),
Permission :: update ( Role :: team ( ID :: custom ( $teamId ), 'developer' )),
Permission :: delete ( Role :: team ( ID :: custom ( $teamId ), 'owner' )),
Permission :: delete ( Role :: team ( ID :: custom ( $teamId ), 'developer' )),
],
2023-07-30 09:51:13 +00:00
'providerInstallationId' => $providerInstallationId ,
2023-06-18 11:38:37 +00:00
'projectId' => $projectId ,
'projectInternalId' => $projectInternalId ,
'provider' => 'github' ,
'organization' => $owner ,
2024-12-27 12:13:14 +00:00
'personal' => $personal ,
'personalRefreshToken' => $refreshToken ,
'personalAccessToken' => $accessToken ,
'personalAccessTokenExpiry' => $accessTokenExpiry ,
2023-06-18 11:38:37 +00:00
]);
2024-12-12 10:30:26 +00:00
$installation = $dbForPlatform -> createDocument ( 'installations' , $installation );
2023-06-18 11:38:37 +00:00
} else {
2023-07-30 09:51:13 +00:00
$installation = $installation
-> setAttribute ( 'organization' , $owner )
2024-12-27 12:13:14 +00:00
-> setAttribute ( 'personal' , $personal )
-> setAttribute ( 'personalRefreshToken' , $refreshToken )
-> setAttribute ( 'personalAccessToken' , $accessToken )
-> setAttribute ( 'personalAccessTokenExpiry' , $accessTokenExpiry );
2024-12-12 10:30:26 +00:00
$installation = $dbForPlatform -> updateDocument ( 'installations' , $installation -> getId (), $installation );
2023-06-18 11:38:37 +00:00
}
2023-06-22 10:59:41 +00:00
} else {
2023-08-05 18:53:42 +00:00
$error = 'Installation of the Appwrite GitHub App on organization accounts is restricted to organization owners. As a member of the organization, you do not have the necessary permissions to install this GitHub App. Please contact the organization owner to create the installation from the Appwrite console.' ;
if ( ! empty ( $redirectFailure )) {
$separator = \str_contains ( $redirectFailure , '?' ) ? '&' : ':' ;
return $response
-> addHeader ( 'Cache-Control' , 'no-store, no-cache, must-revalidate, max-age=0' )
-> addHeader ( 'Pragma' , 'no-cache' )
-> redirect ( $redirectFailure . $separator . \http_build_query ([ 'error' => $error ]));
}
throw new Exception ( Exception :: GENERAL_ARGUMENT_INVALID , $error );
2023-05-22 10:58:13 +00:00
}
$response
-> addHeader ( 'Cache-Control' , 'no-store, no-cache, must-revalidate, max-age=0' )
-> addHeader ( 'Pragma' , 'no-cache' )
2023-08-05 18:53:42 +00:00
-> redirect ( $redirectSuccess );
2023-05-22 10:58:13 +00:00
});
2024-10-08 07:54:40 +00:00
App :: get ( '/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/contents' )
2024-06-26 10:03:27 +00:00
-> desc ( 'Get files and directories of a VCS repository' )
-> groups ([ 'api' , 'vcs' ])
-> label ( 'scope' , 'vcs.read' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'vcs' ,
2025-03-31 05:48:17 +00:00
group : 'repositories' ,
2025-01-17 04:31:39 +00:00
name : 'getRepositoryContents' ,
2025-01-17 07:44:25 +00:00
description : '/docs/references/vcs/get-repository-contents.md' ,
2025-01-17 04:31:39 +00:00
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_VCS_CONTENT_LIST ,
)
]
))
2024-06-26 10:03:27 +00:00
-> param ( 'installationId' , '' , new Text ( 256 ), 'Installation Id' )
-> param ( 'providerRepositoryId' , '' , new Text ( 256 ), 'Repository Id' )
-> param ( 'providerRootDirectory' , '' , new Text ( 256 , 0 ), 'Path to get contents of nested directory' , true )
2025-06-13 10:25:02 +00:00
-> param ( 'providerReference' , '' , new Text ( 256 , 0 ), 'Git reference (branch, tag, commit) to get contents from' , true )
2024-06-26 10:03:27 +00:00
-> inject ( 'gitHub' )
-> inject ( 'response' )
-> inject ( 'project' )
2024-12-12 10:30:26 +00:00
-> inject ( 'dbForPlatform' )
2025-06-13 10:25:02 +00:00
-> action ( function ( string $installationId , string $providerRepositoryId , string $providerRootDirectory , string $providerReference , GitHub $github , Response $response , Document $project , Database $dbForPlatform ) {
2024-12-12 10:30:26 +00:00
$installation = $dbForPlatform -> getDocument ( 'installations' , $installationId );
2024-06-26 10:03:27 +00:00
if ( $installation -> isEmpty ()) {
throw new Exception ( Exception :: INSTALLATION_NOT_FOUND );
}
$providerInstallationId = $installation -> getAttribute ( 'providerInstallationId' );
$privateKey = System :: getEnv ( '_APP_VCS_GITHUB_PRIVATE_KEY' );
$githubAppId = System :: getEnv ( '_APP_VCS_GITHUB_APP_ID' );
$github -> initializeVariables ( $providerInstallationId , $privateKey , $githubAppId );
$owner = $github -> getOwnerName ( $providerInstallationId );
try {
$repositoryName = $github -> getRepositoryName ( $providerRepositoryId ) ? ? '' ;
if ( empty ( $repositoryName )) {
throw new Exception ( Exception :: PROVIDER_REPOSITORY_NOT_FOUND );
}
} catch ( RepositoryNotFound $e ) {
throw new Exception ( Exception :: PROVIDER_REPOSITORY_NOT_FOUND );
}
2025-06-13 10:25:02 +00:00
$contents = $github -> listRepositoryContents ( $owner , $repositoryName , $providerRootDirectory , $providerReference );
2024-06-26 10:03:27 +00:00
$vcsContents = [];
foreach ( $contents as $content ) {
$isDirectory = false ;
2024-09-05 02:25:11 +00:00
if ( $content [ 'type' ] === GitHub :: CONTENTS_DIRECTORY ) {
2024-06-26 10:03:27 +00:00
$isDirectory = true ;
}
$vcsContents [] = new Document ([
'isDirectory' => $isDirectory ,
'name' => $content [ 'name' ] ? ? '' ,
'size' => $content [ 'size' ] ? ? 0
]);
}
$response -> dynamic ( new Document ([
'contents' => $vcsContents
]), Response :: MODEL_VCS_CONTENT_LIST );
});
2025-02-20 07:24:26 +00:00
App :: post ( '/v1/vcs/github/installations/:installationId/detections' )
2025-02-06 18:29:43 +00:00
-> alias ( '/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/detection' )
2025-03-08 15:50:39 +00:00
-> desc ( 'Create repository detection' )
2023-07-28 07:51:53 +00:00
-> groups ([ 'api' , 'vcs' ])
2023-08-30 18:44:33 +00:00
-> label ( 'scope' , 'vcs.write' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'vcs' ,
2025-03-31 05:48:17 +00:00
group : 'repositories' ,
2025-01-17 04:31:39 +00:00
name : 'createRepositoryDetection' ,
2025-01-17 07:44:25 +00:00
description : '/docs/references/vcs/create-repository-detection.md' ,
2025-01-17 04:31:39 +00:00
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
2025-03-08 15:50:39 +00:00
model : Response :: MODEL_DETECTION_RUNTIME ,
2025-02-06 18:29:43 +00:00
),
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
2025-03-08 15:50:39 +00:00
model : Response :: MODEL_DETECTION_FRAMEWORK ,
2025-01-17 04:31:39 +00:00
)
]
))
2023-07-28 07:51:53 +00:00
-> param ( 'installationId' , '' , new Text ( 256 ), 'Installation Id' )
2023-07-30 09:51:13 +00:00
-> param ( 'providerRepositoryId' , '' , new Text ( 256 ), 'Repository Id' )
2025-02-20 07:24:26 +00:00
-> param ( 'type' , '' , new WhiteList ([ 'runtime' , 'framework' ]), 'Detector type. Must be one of the following: runtime, framework' )
2023-08-09 07:46:17 +00:00
-> param ( 'providerRootDirectory' , '' , new Text ( 256 , 0 ), 'Path to Root Directory' , true )
2023-07-28 07:51:53 +00:00
-> inject ( 'gitHub' )
-> inject ( 'response' )
2024-12-12 10:30:26 +00:00
-> inject ( 'dbForPlatform' )
2025-02-20 07:24:26 +00:00
-> action ( function ( string $installationId , string $providerRepositoryId , string $type , string $providerRootDirectory , GitHub $github , Response $response , Database $dbForPlatform ) {
2024-12-12 10:30:26 +00:00
$installation = $dbForPlatform -> getDocument ( 'installations' , $installationId );
2023-07-28 07:51:53 +00:00
if ( $installation -> isEmpty ()) {
throw new Exception ( Exception :: INSTALLATION_NOT_FOUND );
}
2023-07-30 09:51:13 +00:00
$providerInstallationId = $installation -> getAttribute ( 'providerInstallationId' );
2024-04-01 11:02:47 +00:00
$privateKey = System :: getEnv ( '_APP_VCS_GITHUB_PRIVATE_KEY' );
$githubAppId = System :: getEnv ( '_APP_VCS_GITHUB_APP_ID' );
2023-08-09 17:35:23 +00:00
$github -> initializeVariables ( $providerInstallationId , $privateKey , $githubAppId );
2023-07-28 07:51:53 +00:00
2023-07-30 09:51:13 +00:00
$owner = $github -> getOwnerName ( $providerInstallationId );
2023-10-13 13:20:58 +00:00
try {
2023-10-27 14:08:33 +00:00
$repositoryName = $github -> getRepositoryName ( $providerRepositoryId ) ? ? '' ;
if ( empty ( $repositoryName )) {
throw new Exception ( Exception :: PROVIDER_REPOSITORY_NOT_FOUND );
}
2023-10-13 13:20:58 +00:00
} catch ( RepositoryNotFound $e ) {
2023-07-31 06:47:47 +00:00
throw new Exception ( Exception :: PROVIDER_REPOSITORY_NOT_FOUND );
2023-07-28 07:51:53 +00:00
}
2023-07-30 09:51:13 +00:00
$files = $github -> listRepositoryContents ( $owner , $repositoryName , $providerRootDirectory );
2024-06-26 10:03:27 +00:00
$files = \array_column ( $files , 'name' );
2023-08-09 17:35:23 +00:00
$languages = $github -> listRepositoryLanguages ( $owner , $repositoryName );
2023-07-28 07:51:53 +00:00
2025-02-06 18:29:43 +00:00
$detector = new Packager ( $files );
$detector
-> addOption ( new Yarn ())
-> addOption ( new PNPM ())
-> addOption ( new NPM ());
2025-03-08 15:50:39 +00:00
$detection = $detector -> detect ();
2023-07-28 07:51:53 +00:00
2025-03-08 15:50:39 +00:00
$packager = ! \is_null ( $detection ) ? $detection -> getName () : 'npm' ;
2023-07-28 07:51:53 +00:00
2025-02-06 18:29:43 +00:00
if ( $type === 'framework' ) {
2025-03-08 15:50:39 +00:00
$output = new Document ([
2025-02-06 18:29:43 +00:00
'framework' => '' ,
'installCommand' => '' ,
'buildCommand' => '' ,
'outputDirectory' => '' ,
2025-02-20 07:24:26 +00:00
]);
2023-07-28 07:51:53 +00:00
2025-03-08 15:50:39 +00:00
$detector = new Framework ( $files , $packager );
$detector
2025-02-06 18:29:43 +00:00
-> addOption ( new Flutter ())
-> addOption ( new Nuxt ())
-> addOption ( new Astro ())
-> addOption ( new SvelteKit ())
2025-03-10 14:33:11 +00:00
-> addOption ( new NextJs ())
-> addOption ( new Remix ());
2025-02-06 18:29:43 +00:00
2025-03-08 15:50:39 +00:00
$framework = $detector -> detect ();
2025-02-06 18:29:43 +00:00
2025-03-08 15:50:39 +00:00
if ( ! \is_null ( $framework )) {
$output -> setAttribute ( 'installCommand' , $framework -> getInstallCommand ());
$output -> setAttribute ( 'buildCommand' , $framework -> getBuildCommand ());
$output -> setAttribute ( 'outputDirectory' , $framework -> getOutputDirectory ());
2025-03-10 14:16:47 +00:00
$framework = $framework -> getName ();
2025-02-20 07:24:26 +00:00
} else {
$framework = 'other' ;
2025-03-08 15:50:39 +00:00
$output -> setAttribute ( 'installCommand' , '' );
$output -> setAttribute ( 'buildCommand' , '' );
$output -> setAttribute ( 'outputDirectory' , '' );
2025-02-06 18:29:43 +00:00
}
2023-07-28 07:51:53 +00:00
2025-02-20 07:24:26 +00:00
$frameworks = Config :: getParam ( 'frameworks' );
2025-03-17 10:21:52 +00:00
if ( ! \in_array ( $framework , \array_keys ( $frameworks ), true )) {
2025-03-08 15:50:39 +00:00
$framework = 'other' ;
2025-03-03 08:24:07 +00:00
}
2025-03-08 15:50:39 +00:00
$output -> setAttribute ( 'framework' , $framework );
2025-02-06 18:29:43 +00:00
} else {
2025-03-08 15:50:39 +00:00
$output = new Document ([
2025-02-06 18:29:43 +00:00
'runtime' => '' ,
'commands' => '' ,
'entrypoint' => '' ,
2025-02-20 07:24:26 +00:00
]);
2023-07-28 07:51:53 +00:00
2025-02-06 18:29:43 +00:00
$strategies = [
new Strategy ( Strategy :: FILEMATCH ),
new Strategy ( Strategy :: LANGUAGES ),
new Strategy ( Strategy :: EXTENSION ),
];
foreach ( $strategies as $strategy ) {
2025-03-08 15:50:39 +00:00
$detector = new Runtime ( $strategy === Strategy :: LANGUAGES ? $languages : $files , $strategy , $packager );
$detector
2025-02-06 18:29:43 +00:00
-> addOption ( new Node ())
-> addOption ( new Bun ())
-> addOption ( new Deno ())
-> addOption ( new PHP ())
-> addOption ( new Python ())
-> addOption ( new Dart ())
-> addOption ( new Swift ())
-> addOption ( new Ruby ())
-> addOption ( new Java ())
-> addOption ( new CPP ())
-> addOption ( new Dotnet ());
2025-03-08 15:50:39 +00:00
$runtime = $detector -> detect ();
2025-02-06 18:29:43 +00:00
2025-03-08 15:50:39 +00:00
if ( ! \is_null ( $runtime )) {
$output -> setAttribute ( 'commands' , $runtime -> getCommands ());
$output -> setAttribute ( 'entrypoint' , $runtime -> getEntrypoint ());
$runtime = $runtime -> getName ();
2025-02-06 18:29:43 +00:00
break ;
}
}
2023-07-28 07:51:53 +00:00
2025-02-06 18:29:43 +00:00
if ( ! empty ( $runtime )) {
$runtimes = Config :: getParam ( 'runtimes' );
2025-03-08 15:50:39 +00:00
$runtimeWithVersion = '' ;
2025-03-04 08:15:56 +00:00
foreach ( $runtimes as $runtimeKey => $runtimeConfig ) {
if ( $runtimeConfig [ 'key' ] === $runtime ) {
2025-03-08 15:50:39 +00:00
$runtimeWithVersion = $runtimeKey ;
2025-03-03 08:24:07 +00:00
}
}
2023-07-30 08:30:47 +00:00
2025-03-08 15:50:39 +00:00
if ( empty ( $runtimeWithVersion )) {
2025-03-04 08:15:56 +00:00
throw new Exception ( Exception :: FUNCTION_RUNTIME_NOT_DETECTED );
}
2023-07-28 07:51:53 +00:00
2025-03-08 15:50:39 +00:00
$output -> setAttribute ( 'runtime' , $runtimeWithVersion );
2025-02-20 07:24:26 +00:00
} else {
2025-03-03 08:24:07 +00:00
throw new Exception ( Exception :: FUNCTION_RUNTIME_NOT_DETECTED );
2025-02-06 18:29:43 +00:00
}
}
2025-03-08 15:50:39 +00:00
$response -> dynamic ( $output , $type === 'framework' ? Response :: MODEL_DETECTION_FRAMEWORK : Response :: MODEL_DETECTION_RUNTIME );
2023-07-28 07:51:53 +00:00
});
2024-10-08 07:54:40 +00:00
App :: get ( '/v1/vcs/github/installations/:installationId/providerRepositories' )
-> desc ( 'List repositories' )
2023-05-22 10:58:13 +00:00
-> groups ([ 'api' , 'vcs' ])
2023-08-30 18:44:33 +00:00
-> label ( 'scope' , 'vcs.read' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'vcs' ,
2025-03-31 05:48:17 +00:00
group : 'repositories' ,
2025-01-17 04:31:39 +00:00
name : 'listRepositories' ,
2025-01-17 07:44:25 +00:00
description : '/docs/references/vcs/list-repositories.md' ,
2025-01-17 04:31:39 +00:00
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
2025-03-08 15:50:39 +00:00
model : Response :: MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST ,
2025-02-21 09:52:15 +00:00
),
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
2025-03-08 15:50:39 +00:00
model : Response :: MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST ,
2025-01-17 04:31:39 +00:00
)
]
))
2023-05-22 10:58:13 +00:00
-> param ( 'installationId' , '' , new Text ( 256 ), 'Installation Id' )
2025-02-20 07:24:26 +00:00
-> param ( 'type' , '' , new WhiteList ([ 'runtime' , 'framework' ]), 'Detector type. Must be one of the following: runtime, framework' )
2023-05-22 10:58:13 +00:00
-> param ( 'search' , '' , new Text ( 256 ), 'Search term to filter your list results. Max length: 256 chars.' , true )
2023-05-26 08:44:08 +00:00
-> inject ( 'gitHub' )
2023-05-22 10:58:13 +00:00
-> inject ( 'response' )
2024-12-12 10:30:26 +00:00
-> inject ( 'dbForPlatform' )
2025-02-20 07:24:26 +00:00
-> action ( function ( string $installationId , string $type , string $search , GitHub $github , Response $response , Database $dbForPlatform ) {
2023-05-22 10:58:13 +00:00
if ( empty ( $search )) {
$search = " " ;
}
2024-12-12 10:30:26 +00:00
$installation = $dbForPlatform -> getDocument ( 'installations' , $installationId );
2023-05-22 10:58:13 +00:00
if ( $installation -> isEmpty ()) {
throw new Exception ( Exception :: INSTALLATION_NOT_FOUND );
}
2023-07-30 09:51:13 +00:00
$providerInstallationId = $installation -> getAttribute ( 'providerInstallationId' );
2024-04-01 11:02:47 +00:00
$privateKey = System :: getEnv ( '_APP_VCS_GITHUB_PRIVATE_KEY' );
$githubAppId = System :: getEnv ( '_APP_VCS_GITHUB_APP_ID' );
2023-08-09 17:35:23 +00:00
$github -> initializeVariables ( $providerInstallationId , $privateKey , $githubAppId );
2023-05-22 10:58:13 +00:00
$page = 1 ;
2023-08-29 17:41:40 +00:00
$perPage = 4 ;
2023-05-22 10:58:13 +00:00
2023-08-29 17:37:34 +00:00
$owner = $github -> getOwnerName ( $providerInstallationId );
2023-08-29 17:41:40 +00:00
$repos = $github -> searchRepositories ( $owner , $page , $perPage , $search );
2023-05-22 10:58:13 +00:00
2023-07-30 13:24:10 +00:00
$repos = \array_map ( function ( $repo ) use ( $installation ) {
2023-07-24 10:11:30 +00:00
$repo [ 'id' ] = \strval ( $repo [ 'id' ] ? ? '' );
$repo [ 'pushedAt' ] = $repo [ 'pushed_at' ] ? ? null ;
$repo [ 'provider' ] = $installation -> getAttribute ( 'provider' , '' ) ? ? '' ;
$repo [ 'organization' ] = $installation -> getAttribute ( 'organization' , '' ) ? ? '' ;
2023-08-07 15:37:36 +00:00
return $repo ;
2023-05-27 11:55:34 +00:00
}, $repos );
2025-02-20 07:24:26 +00:00
$repos = batch ( \array_map ( function ( $repo ) use ( $type , $github ) {
2025-02-17 12:24:13 +00:00
return function () use ( $repo , $type , $github ) {
$files = $github -> listRepositoryContents ( $repo [ 'organization' ], $repo [ 'name' ], '' );
$files = \array_column ( $files , 'name' );
2025-03-03 08:24:07 +00:00
$detector = new Packager ( $files );
$detector
-> addOption ( new Yarn ())
-> addOption ( new PNPM ())
-> addOption ( new NPM ());
2025-03-08 15:50:39 +00:00
$detection = $detector -> detect ();
2025-03-03 08:24:07 +00:00
2025-03-08 15:50:39 +00:00
$packager = ! \is_null ( $detection ) ? $detection -> getName () : 'npm' ;
2025-03-03 08:24:07 +00:00
2025-02-17 12:24:13 +00:00
if ( $type === 'framework' ) {
2025-03-08 15:50:39 +00:00
$frameworkDetector = new Framework ( $files , $packager );
2025-03-03 08:24:07 +00:00
$frameworkDetector
-> addOption ( new Flutter ())
-> addOption ( new Nuxt ())
-> addOption ( new Astro ())
-> addOption ( new SvelteKit ())
2025-03-10 14:33:11 +00:00
-> addOption ( new NextJs ())
-> addOption ( new Remix ());
2025-03-03 08:24:07 +00:00
$detectedFramework = $frameworkDetector -> detect ();
2025-03-08 15:50:39 +00:00
if ( ! \is_null ( $detectedFramework )) {
2025-03-03 08:24:07 +00:00
$framework = $detectedFramework -> getName ();
} else {
$framework = 'other' ;
}
2025-02-20 07:24:26 +00:00
2025-03-03 08:24:07 +00:00
$frameworks = Config :: getParam ( 'frameworks' );
2025-03-17 10:21:52 +00:00
if ( ! \in_array ( $framework , \array_keys ( $frameworks ), true )) {
2025-03-08 15:50:39 +00:00
$framework = 'other' ;
2025-02-06 18:29:43 +00:00
}
2025-03-08 15:50:39 +00:00
$repo [ 'framework' ] = $framework ;
2025-02-17 12:24:13 +00:00
} else {
2023-08-09 17:35:23 +00:00
$languages = $github -> listRepositoryLanguages ( $repo [ 'organization' ], $repo [ 'name' ]);
2023-08-01 08:40:16 +00:00
2025-03-05 08:18:44 +00:00
$strategies = [
new Strategy ( Strategy :: FILEMATCH ),
new Strategy ( Strategy :: LANGUAGES ),
new Strategy ( Strategy :: EXTENSION ),
];
foreach ( $strategies as $strategy ) {
2025-03-08 19:44:54 +00:00
$detector = new Runtime ( $strategy === Strategy :: LANGUAGES ? $languages : $files , $strategy , $packager );
$detector
2025-03-05 08:18:44 +00:00
-> addOption ( new Node ())
-> addOption ( new Bun ())
-> addOption ( new Deno ())
-> addOption ( new PHP ())
-> addOption ( new Python ())
-> addOption ( new Dart ())
-> addOption ( new Swift ())
-> addOption ( new Ruby ())
-> addOption ( new Java ())
-> addOption ( new CPP ())
-> addOption ( new Dotnet ());
2025-03-08 19:44:54 +00:00
$runtime = $detector -> detect ();
2025-03-05 08:18:44 +00:00
2025-03-08 15:50:39 +00:00
if ( ! \is_null ( $runtime )) {
$runtime = $runtime -> getName ();
2025-03-05 08:18:44 +00:00
break ;
2025-02-17 12:24:13 +00:00
}
2025-03-05 08:18:44 +00:00
}
2025-02-06 18:29:43 +00:00
2025-03-05 08:18:44 +00:00
if ( ! empty ( $runtime )) {
$runtimes = Config :: getParam ( 'runtimes' );
2025-03-08 15:50:39 +00:00
$runtimeWithVersion = '' ;
2025-03-05 08:18:44 +00:00
foreach ( $runtimes as $runtimeKey => $runtimeConfig ) {
if ( $runtimeConfig [ 'key' ] === $runtime ) {
2025-03-08 15:50:39 +00:00
$runtimeWithVersion = $runtimeKey ;
2025-03-03 08:24:07 +00:00
}
2025-02-17 12:24:13 +00:00
}
2025-03-05 08:18:44 +00:00
2025-03-08 15:50:39 +00:00
$repo [ 'runtime' ] = $runtimeWithVersion ? ? '' ;
2025-02-06 18:29:43 +00:00
}
2023-08-01 08:40:16 +00:00
}
2023-07-30 13:24:10 +00:00
return $repo ;
};
}, $repos ));
2023-08-07 15:37:36 +00:00
$repos = \array_map ( function ( $repo ) {
return new Document ( $repo );
}, $repos );
2023-05-22 10:58:13 +00:00
$response -> dynamic ( new Document ([
2025-02-21 09:52:15 +00:00
$type === 'framework' ? 'frameworkProviderRepositories' : 'runtimeProviderRepositories' => $repos ,
2023-05-22 10:58:13 +00:00
'total' => \count ( $repos ),
2025-03-08 15:50:39 +00:00
]), ( $type === 'framework' ) ? Response :: MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST : Response :: MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST );
2023-05-22 10:58:13 +00:00
});
2024-10-08 07:54:40 +00:00
App :: post ( '/v1/vcs/github/installations/:installationId/providerRepositories' )
2023-06-18 11:38:37 +00:00
-> desc ( 'Create repository' )
-> groups ([ 'api' , 'vcs' ])
2023-08-30 18:44:33 +00:00
-> label ( 'scope' , 'vcs.write' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'vcs' ,
2025-03-31 05:48:17 +00:00
group : 'repositories' ,
2025-01-17 04:31:39 +00:00
name : 'createRepository' ,
2025-01-17 07:44:25 +00:00
description : '/docs/references/vcs/create-repository.md' ,
2025-01-17 04:31:39 +00:00
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_PROVIDER_REPOSITORY ,
)
]
))
2023-06-18 11:38:37 +00:00
-> param ( 'installationId' , '' , new Text ( 256 ), 'Installation Id' )
-> param ( 'name' , '' , new Text ( 256 ), 'Repository name (slug)' )
-> param ( 'private' , '' , new Boolean ( false ), 'Mark repository public or private' )
-> inject ( 'gitHub' )
-> inject ( 'user' )
-> inject ( 'response' )
-> inject ( 'project' )
2024-12-12 10:30:26 +00:00
-> inject ( 'dbForPlatform' )
-> action ( function ( string $installationId , string $name , bool $private , GitHub $github , Document $user , Response $response , Document $project , Database $dbForPlatform ) {
$installation = $dbForPlatform -> getDocument ( 'installations' , $installationId );
2023-06-18 11:38:37 +00:00
if ( $installation -> isEmpty ()) {
throw new Exception ( Exception :: INSTALLATION_NOT_FOUND );
}
if ( $installation -> getAttribute ( 'personal' , false ) === true ) {
2024-04-01 11:02:47 +00:00
$oauth2 = new OAuth2Github ( System :: getEnv ( '_APP_VCS_GITHUB_CLIENT_ID' , '' ), System :: getEnv ( '_APP_VCS_GITHUB_CLIENT_SECRET' , '' ), " " );
2023-06-18 11:38:37 +00:00
2024-12-27 12:13:14 +00:00
$accessToken = $installation -> getAttribute ( 'personalAccessToken' );
$refreshToken = $installation -> getAttribute ( 'personalRefreshToken' );
$accessTokenExpiry = $installation -> getAttribute ( 'personalAccessTokenExpiry' );
2023-06-18 11:38:37 +00:00
2024-12-30 13:43:28 +00:00
if ( empty ( $accessToken ) || empty ( $refreshToken ) || empty ( $accessTokenExpiry )) {
$identity = $dbForPlatform -> findOne ( 'identities' , [
Query :: equal ( 'provider' , [ 'github' ]),
2025-05-26 05:42:11 +00:00
Query :: equal ( 'userInternalId' , [ $user -> getSequence ()]),
2024-12-30 13:43:28 +00:00
]);
if ( $identity -> isEmpty ()) {
throw new Exception ( Exception :: USER_IDENTITY_NOT_FOUND );
}
$accessToken = $accessToken ? ? $identity -> getAttribute ( 'providerAccessToken' );
$refreshToken = $refreshToken ? ? $identity -> getAttribute ( 'providerRefreshToken' );
$accessTokenExpiry = $accessTokenExpiry ? ? $identity -> getAttribute ( 'providerAccessTokenExpiry' );
}
2023-06-18 11:38:37 +00:00
$isExpired = new \DateTime ( $accessTokenExpiry ) < new \DateTime ( 'now' );
if ( $isExpired ) {
$oauth2 -> refreshTokens ( $refreshToken );
$accessToken = $oauth2 -> getAccessToken ( '' );
$refreshToken = $oauth2 -> getRefreshToken ( '' );
$verificationId = $oauth2 -> getUserID ( $accessToken );
if ( empty ( $verificationId )) {
throw new Exception ( Exception :: GENERAL_RATE_LIMIT_EXCEEDED , " Another request is currently refreshing OAuth token. Please try again. " );
}
2024-12-27 12:13:14 +00:00
$installation = $installation
-> setAttribute ( 'personalAccessToken' , $accessToken )
-> setAttribute ( 'personalRefreshToken' , $refreshToken )
-> setAttribute ( 'personalAccessTokenExpiry' , DateTime :: addSeconds ( new \DateTime (), ( int ) $oauth2 -> getAccessTokenExpiry ( '' )));
2023-06-18 11:38:37 +00:00
2024-12-27 12:13:14 +00:00
$dbForPlatform -> updateDocument ( 'installations' , $installation -> getId (), $installation );
2023-06-18 11:38:37 +00:00
}
2023-08-17 13:54:57 +00:00
try {
$repository = $oauth2 -> createRepository ( $accessToken , $name , $private );
} catch ( Exception $exception ) {
throw new Exception ( Exception :: GENERAL_PROVIDER_FAILURE , " GitHub failed to process the request: " . $exception -> getMessage ());
}
2023-06-18 11:38:37 +00:00
} else {
2023-07-30 09:51:13 +00:00
$providerInstallationId = $installation -> getAttribute ( 'providerInstallationId' );
2024-04-01 11:02:47 +00:00
$privateKey = System :: getEnv ( '_APP_VCS_GITHUB_PRIVATE_KEY' );
$githubAppId = System :: getEnv ( '_APP_VCS_GITHUB_APP_ID' );
2023-08-09 17:35:23 +00:00
$github -> initializeVariables ( $providerInstallationId , $privateKey , $githubAppId );
2023-07-30 09:51:13 +00:00
$owner = $github -> getOwnerName ( $providerInstallationId );
2023-06-18 11:38:37 +00:00
2023-08-17 13:54:57 +00:00
try {
$repository = $github -> createRepository ( $owner , $name , $private );
} catch ( Exception $exception ) {
throw new Exception ( Exception :: GENERAL_PROVIDER_FAILURE , " GitHub failed to process the request: " . $exception -> getMessage ());
}
2023-06-18 11:38:37 +00:00
}
2023-08-08 09:46:43 +00:00
2023-08-04 08:56:46 +00:00
if ( isset ( $repository [ 'errors' ])) {
$message = $repository [ 'message' ] ? ? 'Unknown error.' ;
if ( isset ( $repository [ 'errors' ][ 0 ])) {
$message .= ' ' . $repository [ 'errors' ][ 0 ][ 'message' ];
}
throw new Exception ( Exception :: GENERAL_ARGUMENT_INVALID , 'Provider Error: ' . $message );
}
2023-08-19 12:51:27 +00:00
if ( isset ( $repository [ 'message' ])) {
throw new Exception ( Exception :: GENERAL_ARGUMENT_INVALID , 'Provider Error: ' . $repository [ 'message' ]);
}
2023-07-28 07:40:19 +00:00
$repository [ 'id' ] = \strval ( $repository [ 'id' ]) ? ? '' ;
$repository [ 'pushedAt' ] = $repository [ 'pushed_at' ] ? ? '' ;
2023-06-22 10:59:41 +00:00
$repository [ 'organization' ] = $installation -> getAttribute ( 'organization' , '' );
$repository [ 'provider' ] = $installation -> getAttribute ( 'provider' , '' );
2023-06-18 11:38:37 +00:00
2023-07-31 06:47:47 +00:00
$response -> dynamic ( new Document ( $repository ), Response :: MODEL_PROVIDER_REPOSITORY );
2023-06-18 11:38:37 +00:00
});
2024-10-08 07:54:40 +00:00
App :: get ( '/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId' )
2023-06-07 15:50:32 +00:00
-> desc ( 'Get repository' )
-> groups ([ 'api' , 'vcs' ])
2023-08-30 18:44:33 +00:00
-> label ( 'scope' , 'vcs.read' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'vcs' ,
2025-03-31 05:48:17 +00:00
group : 'repositories' ,
2025-01-17 04:31:39 +00:00
name : 'getRepository' ,
2025-01-17 07:44:25 +00:00
description : '/docs/references/vcs/get-repository.md' ,
2025-01-17 04:31:39 +00:00
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_PROVIDER_REPOSITORY ,
)
]
))
2023-06-07 15:50:32 +00:00
-> param ( 'installationId' , '' , new Text ( 256 ), 'Installation Id' )
2023-07-30 09:51:13 +00:00
-> param ( 'providerRepositoryId' , '' , new Text ( 256 ), 'Repository Id' )
2023-06-07 15:50:32 +00:00
-> inject ( 'gitHub' )
-> inject ( 'response' )
-> inject ( 'project' )
2024-12-12 10:30:26 +00:00
-> inject ( 'dbForPlatform' )
-> action ( function ( string $installationId , string $providerRepositoryId , GitHub $github , Response $response , Document $project , Database $dbForPlatform ) {
$installation = $dbForPlatform -> getDocument ( 'installations' , $installationId );
2023-06-07 15:50:32 +00:00
if ( $installation -> isEmpty ()) {
throw new Exception ( Exception :: INSTALLATION_NOT_FOUND );
}
2023-07-30 09:51:13 +00:00
$providerInstallationId = $installation -> getAttribute ( 'providerInstallationId' );
2024-04-01 11:02:47 +00:00
$privateKey = System :: getEnv ( '_APP_VCS_GITHUB_PRIVATE_KEY' );
$githubAppId = System :: getEnv ( '_APP_VCS_GITHUB_APP_ID' );
2023-08-09 17:35:23 +00:00
$github -> initializeVariables ( $providerInstallationId , $privateKey , $githubAppId );
2023-06-07 15:50:32 +00:00
2023-07-30 09:51:13 +00:00
$owner = $github -> getOwnerName ( $providerInstallationId ) ? ? '' ;
2023-10-13 13:20:58 +00:00
try {
$repositoryName = $github -> getRepositoryName ( $providerRepositoryId ) ? ? '' ;
2023-10-27 14:08:33 +00:00
if ( empty ( $repositoryName )) {
throw new Exception ( Exception :: PROVIDER_REPOSITORY_NOT_FOUND );
}
2023-10-13 13:20:58 +00:00
} catch ( RepositoryNotFound $e ) {
2023-07-31 06:47:47 +00:00
throw new Exception ( Exception :: PROVIDER_REPOSITORY_NOT_FOUND );
2023-06-07 15:50:32 +00:00
}
$repository = $github -> getRepository ( $owner , $repositoryName );
2023-07-28 07:40:19 +00:00
$repository [ 'id' ] = \strval ( $repository [ 'id' ]) ? ? '' ;
$repository [ 'pushedAt' ] = $repository [ 'pushed_at' ] ? ? '' ;
2023-06-18 14:08:53 +00:00
$repository [ 'organization' ] = $installation -> getAttribute ( 'organization' , '' );
$repository [ 'provider' ] = $installation -> getAttribute ( 'provider' , '' );
2025-07-24 14:19:52 +00:00
$repository [ 'defaultBranch' ] = $repository [ 'default_branch' ] ? ? '' ;
2023-06-08 15:24:27 +00:00
2023-07-31 06:47:47 +00:00
$response -> dynamic ( new Document ( $repository ), Response :: MODEL_PROVIDER_REPOSITORY );
2023-06-07 15:50:32 +00:00
});
2024-10-08 07:54:40 +00:00
App :: get ( '/v1/vcs/github/installations/:installationId/providerRepositories/:providerRepositoryId/branches' )
-> desc ( 'List repository branches' )
2023-05-25 22:29:08 +00:00
-> groups ([ 'api' , 'vcs' ])
2023-08-30 18:44:33 +00:00
-> label ( 'scope' , 'vcs.read' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'vcs' ,
2025-03-31 05:48:17 +00:00
group : 'repositories' ,
2025-01-17 04:31:39 +00:00
name : 'listRepositoryBranches' ,
2025-01-17 07:44:25 +00:00
description : '/docs/references/vcs/list-repository-branches.md' ,
2025-01-17 04:31:39 +00:00
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_BRANCH_LIST ,
)
]
))
2023-05-25 22:29:08 +00:00
-> param ( 'installationId' , '' , new Text ( 256 ), 'Installation Id' )
2023-07-30 09:51:13 +00:00
-> param ( 'providerRepositoryId' , '' , new Text ( 256 ), 'Repository Id' )
2023-05-26 08:44:08 +00:00
-> inject ( 'gitHub' )
2023-05-25 22:29:08 +00:00
-> inject ( 'response' )
-> inject ( 'project' )
2024-12-12 10:30:26 +00:00
-> inject ( 'dbForPlatform' )
-> action ( function ( string $installationId , string $providerRepositoryId , GitHub $github , Response $response , Document $project , Database $dbForPlatform ) {
$installation = $dbForPlatform -> getDocument ( 'installations' , $installationId );
2023-05-25 22:29:08 +00:00
if ( $installation -> isEmpty ()) {
throw new Exception ( Exception :: INSTALLATION_NOT_FOUND );
}
2023-07-30 09:51:13 +00:00
$providerInstallationId = $installation -> getAttribute ( 'providerInstallationId' );
2024-04-01 11:02:47 +00:00
$privateKey = System :: getEnv ( '_APP_VCS_GITHUB_PRIVATE_KEY' );
$githubAppId = System :: getEnv ( '_APP_VCS_GITHUB_APP_ID' );
2023-08-09 17:35:23 +00:00
$github -> initializeVariables ( $providerInstallationId , $privateKey , $githubAppId );
2023-05-25 22:29:08 +00:00
2023-07-30 09:51:13 +00:00
$owner = $github -> getOwnerName ( $providerInstallationId ) ? ? '' ;
2023-10-13 13:20:58 +00:00
try {
$repositoryName = $github -> getRepositoryName ( $providerRepositoryId ) ? ? '' ;
2023-10-27 14:08:33 +00:00
if ( empty ( $repositoryName )) {
throw new Exception ( Exception :: PROVIDER_REPOSITORY_NOT_FOUND );
}
2023-10-13 13:20:58 +00:00
} catch ( RepositoryNotFound $e ) {
2023-07-31 06:47:47 +00:00
throw new Exception ( Exception :: PROVIDER_REPOSITORY_NOT_FOUND );
2023-07-28 07:40:19 +00:00
}
$branches = $github -> listBranches ( $owner , $repositoryName ) ? ? [];
2023-05-25 22:29:08 +00:00
$response -> dynamic ( new Document ([
'branches' => \array_map ( function ( $branch ) {
2023-08-24 11:04:49 +00:00
return new Document ([ 'name' => $branch ]);
2023-05-25 22:29:08 +00:00
}, $branches ),
'total' => \count ( $branches ),
]), Response :: MODEL_BRANCH_LIST );
});
2024-10-08 07:54:40 +00:00
App :: post ( '/v1/vcs/github/events' )
-> desc ( 'Create event' )
2023-05-22 10:58:13 +00:00
-> groups ([ 'api' , 'vcs' ])
2023-08-30 18:44:33 +00:00
-> label ( 'scope' , 'public' )
2023-05-26 08:44:08 +00:00
-> inject ( 'gitHub' )
2023-05-22 10:58:13 +00:00
-> inject ( 'request' )
-> inject ( 'response' )
2024-12-12 10:30:26 +00:00
-> inject ( 'dbForPlatform' )
2023-05-23 04:37:25 +00:00
-> inject ( 'getProjectDB' )
2023-10-09 10:28:06 +00:00
-> inject ( 'queueForBuilds' )
2023-05-22 10:58:13 +00:00
-> action (
2024-12-12 10:30:26 +00:00
function ( GitHub $github , Request $request , Response $response , Database $dbForPlatform , callable $getProjectDB , Build $queueForBuilds ) use ( $createGitDeployments ) {
2023-05-22 10:58:13 +00:00
$payload = $request -> getRawPayload ();
2023-09-06 17:04:03 +00:00
$signatureRemote = $request -> getHeader ( 'x-hub-signature-256' , '' );
2024-04-01 11:02:47 +00:00
$signatureLocal = System :: getEnv ( '_APP_VCS_GITHUB_WEBHOOK_SECRET' , '' );
2023-06-15 10:37:28 +00:00
2023-09-06 17:04:03 +00:00
$valid = empty ( $signatureRemote ) ? true : $github -> validateWebhookEvent ( $payload , $signatureRemote , $signatureLocal );
2023-06-15 10:38:03 +00:00
if ( ! $valid ) {
2023-09-06 17:57:46 +00:00
throw new Exception ( Exception :: GENERAL_ACCESS_FORBIDDEN , " Invalid webhook payload signature. Please make sure the webhook secret has same value in your GitHub app and in the _APP_VCS_GITHUB_WEBHOOK_SECRET environment variable " );
2023-06-15 10:37:28 +00:00
}
$event = $request -> getHeader ( 'x-github-event' , '' );
2024-04-01 11:02:47 +00:00
$privateKey = System :: getEnv ( '_APP_VCS_GITHUB_PRIVATE_KEY' );
$githubAppId = System :: getEnv ( '_APP_VCS_GITHUB_APP_ID' );
2023-07-24 10:11:30 +00:00
$parsedPayload = $github -> getEvent ( $event , $payload );
2023-05-22 10:58:13 +00:00
if ( $event == $github :: EVENT_PUSH ) {
2023-08-19 12:54:43 +00:00
$providerBranchCreated = $parsedPayload [ " branchCreated " ] ? ? false ;
2025-06-02 08:34:03 +00:00
$providerBranchDeleted = $parsedPayload [ " branchDeleted " ] ? ? false ;
2023-07-30 09:51:13 +00:00
$providerBranch = $parsedPayload [ " branch " ] ? ? '' ;
2023-08-09 17:35:23 +00:00
$providerBranchUrl = $parsedPayload [ " branchUrl " ] ? ? '' ;
2023-07-30 09:51:13 +00:00
$providerRepositoryId = $parsedPayload [ " repositoryId " ] ? ? '' ;
2023-08-09 10:54:46 +00:00
$providerRepositoryName = $parsedPayload [ " repositoryName " ] ? ? '' ;
2023-07-30 09:51:13 +00:00
$providerInstallationId = $parsedPayload [ " installationId " ] ? ? '' ;
2023-08-09 10:54:46 +00:00
$providerRepositoryUrl = $parsedPayload [ " repositoryUrl " ] ? ? '' ;
$providerCommitHash = $parsedPayload [ " commitHash " ] ? ? '' ;
$providerRepositoryOwner = $parsedPayload [ " owner " ] ? ? '' ;
2025-07-23 04:23:09 +00:00
$providerCommitAuthorName = $parsedPayload [ " headCommitAuthorName " ] ? ? '' ;
2025-07-22 15:09:36 +00:00
$providerCommitAuthorEmail = $parsedPayload [ " headCommitAuthorEmail " ] ? ? '' ;
2023-08-09 17:35:23 +00:00
$providerCommitAuthorUrl = $parsedPayload [ " authorUrl " ] ? ? '' ;
2023-08-09 10:54:46 +00:00
$providerCommitMessage = $parsedPayload [ " headCommitMessage " ] ? ? '' ;
$providerCommitUrl = $parsedPayload [ " headCommitUrl " ] ? ? '' ;
2023-05-22 10:58:13 +00:00
2023-08-09 17:35:23 +00:00
$github -> initializeVariables ( $providerInstallationId , $privateKey , $githubAppId );
2023-05-29 11:51:03 +00:00
2024-10-25 12:41:14 +00:00
//find resourceId from relevant resources table
2024-12-12 10:30:26 +00:00
$repositories = Authorization :: skip ( fn () => $dbForPlatform -> find ( 'repositories' , [
2023-07-30 09:51:13 +00:00
Query :: equal ( 'providerRepositoryId' , [ $providerRepositoryId ]),
2023-05-22 10:58:13 +00:00
Query :: limit ( 100 ),
2023-11-14 15:50:59 +00:00
]));
2023-05-22 10:58:13 +00:00
2025-07-22 15:09:36 +00:00
// create new deployment only on push (not committed by us) and not when branch is created or deleted
if ( $providerCommitAuthorEmail !== APP_VCS_GITHUB_EMAIL && ! $providerBranchCreated && ! $providerBranchDeleted ) {
2025-07-23 04:23:09 +00:00
$createGitDeployments ( $github , $providerInstallationId , $repositories , $providerBranch , $providerBranchUrl , $providerRepositoryName , $providerRepositoryUrl , $providerRepositoryOwner , $providerCommitHash , $providerCommitAuthorName , $providerCommitAuthorUrl , $providerCommitMessage , $providerCommitUrl , '' , false , $dbForPlatform , $queueForBuilds , $getProjectDB , $request );
2023-08-09 15:53:58 +00:00
}
2023-05-22 10:58:13 +00:00
} elseif ( $event == $github :: EVENT_INSTALLATION ) {
if ( $parsedPayload [ " action " ] == " deleted " ) {
2024-10-25 12:41:14 +00:00
// TODO: Use worker for this job instead (update function/site as well)
2023-07-30 09:51:13 +00:00
$providerInstallationId = $parsedPayload [ " installationId " ];
2023-05-22 10:58:13 +00:00
2024-12-12 10:30:26 +00:00
$installations = $dbForPlatform -> find ( 'installations' , [
2023-07-30 09:51:13 +00:00
Query :: equal ( 'providerInstallationId' , [ $providerInstallationId ]),
2023-05-22 10:58:13 +00:00
Query :: limit ( 1000 )
]);
2023-07-30 09:51:13 +00:00
foreach ( $installations as $installation ) {
2024-12-12 10:30:26 +00:00
$repositories = Authorization :: skip ( fn () => $dbForPlatform -> find ( 'repositories' , [
2025-05-26 05:42:11 +00:00
Query :: equal ( 'installationInternalId' , [ $installation -> getSequence ()]),
2023-05-22 10:58:13 +00:00
Query :: limit ( 1000 )
2023-11-14 15:50:59 +00:00
]));
2023-05-22 10:58:13 +00:00
2023-07-30 09:51:13 +00:00
foreach ( $repositories as $repository ) {
2024-12-12 10:30:26 +00:00
Authorization :: skip ( fn () => $dbForPlatform -> deleteDocument ( 'repositories' , $repository -> getId ()));
2023-05-22 10:58:13 +00:00
}
2024-12-12 10:30:26 +00:00
$dbForPlatform -> deleteDocument ( 'installations' , $installation -> getId ());
2023-05-22 10:58:13 +00:00
}
}
} elseif ( $event == $github :: EVENT_PULL_REQUEST ) {
2023-06-28 08:48:10 +00:00
if ( $parsedPayload [ " action " ] == " opened " || $parsedPayload [ " action " ] == " reopened " || $parsedPayload [ " action " ] == " synchronize " ) {
2023-07-30 09:51:13 +00:00
$providerBranch = $parsedPayload [ " branch " ] ? ? '' ;
2023-08-09 17:35:23 +00:00
$providerBranchUrl = $parsedPayload [ " branchUrl " ] ? ? '' ;
2023-07-30 09:51:13 +00:00
$providerRepositoryId = $parsedPayload [ " repositoryId " ] ? ? '' ;
2023-08-09 10:54:46 +00:00
$providerRepositoryName = $parsedPayload [ " repositoryName " ] ? ? '' ;
2023-07-30 09:51:13 +00:00
$providerInstallationId = $parsedPayload [ " installationId " ] ? ? '' ;
2023-08-09 10:54:46 +00:00
$providerRepositoryUrl = $parsedPayload [ " repositoryUrl " ] ? ? '' ;
2023-07-30 09:51:13 +00:00
$providerPullRequestId = $parsedPayload [ " pullRequestNumber " ] ? ? '' ;
2023-08-09 10:54:46 +00:00
$providerCommitHash = $parsedPayload [ " commitHash " ] ? ? '' ;
$providerRepositoryOwner = $parsedPayload [ " owner " ] ? ? '' ;
2023-07-28 07:40:19 +00:00
$external = $parsedPayload [ " external " ] ? ? true ;
2023-08-09 10:54:46 +00:00
$providerCommitUrl = $parsedPayload [ " headCommitUrl " ] ? ? '' ;
2023-08-09 17:35:23 +00:00
$providerCommitAuthorUrl = $parsedPayload [ " authorUrl " ] ? ? '' ;
2023-05-29 11:51:03 +00:00
2023-06-28 11:31:35 +00:00
// Ignore sync for non-external. We handle it in push webhook
2023-07-01 06:46:21 +00:00
if ( ! $external && $parsedPayload [ " action " ] == " synchronize " ) {
2023-06-28 11:31:35 +00:00
return $response -> json ( $parsedPayload );
}
2023-08-09 17:35:23 +00:00
$github -> initializeVariables ( $providerInstallationId , $privateKey , $githubAppId );
2023-05-22 10:58:13 +00:00
2023-08-09 10:54:46 +00:00
$commitDetails = $github -> getCommit ( $providerRepositoryOwner , $providerRepositoryName , $providerCommitHash );
$providerCommitAuthor = $commitDetails [ " commitAuthor " ] ? ? '' ;
$providerCommitMessage = $commitDetails [ " commitMessage " ] ? ? '' ;
2024-12-12 10:30:26 +00:00
$repositories = Authorization :: skip ( fn () => $dbForPlatform -> find ( 'repositories' , [
2023-07-30 09:51:13 +00:00
Query :: equal ( 'providerRepositoryId' , [ $providerRepositoryId ]),
2023-05-22 10:58:13 +00:00
Query :: orderDesc ( '$createdAt' )
2023-11-14 15:50:59 +00:00
]));
2023-05-22 10:58:13 +00:00
2024-12-12 10:30:26 +00:00
$createGitDeployments ( $github , $providerInstallationId , $repositories , $providerBranch , $providerBranchUrl , $providerRepositoryName , $providerRepositoryUrl , $providerRepositoryOwner , $providerCommitHash , $providerCommitAuthor , $providerCommitAuthorUrl , $providerCommitMessage , $providerCommitUrl , $providerPullRequestId , $external , $dbForPlatform , $queueForBuilds , $getProjectDB , $request );
2023-06-28 08:48:10 +00:00
} elseif ( $parsedPayload [ " action " ] == " closed " ) {
// Allowed external contributions cleanup
2023-07-30 09:51:13 +00:00
$providerRepositoryId = $parsedPayload [ " repositoryId " ] ? ? '' ;
$providerPullRequestId = $parsedPayload [ " pullRequestNumber " ] ? ? '' ;
2023-07-28 07:40:19 +00:00
$external = $parsedPayload [ " external " ] ? ? true ;
2023-06-28 08:48:10 +00:00
if ( $external ) {
2024-12-12 10:30:26 +00:00
$repositories = Authorization :: skip ( fn () => $dbForPlatform -> find ( 'repositories' , [
2023-07-30 09:51:13 +00:00
Query :: equal ( 'providerRepositoryId' , [ $providerRepositoryId ]),
2023-06-28 08:48:10 +00:00
Query :: orderDesc ( '$createdAt' )
2023-11-14 15:50:59 +00:00
]));
2023-06-28 08:48:10 +00:00
2023-07-30 09:51:13 +00:00
foreach ( $repositories as $repository ) {
$providerPullRequestIds = $repository -> getAttribute ( 'providerPullRequestIds' , []);
2023-06-28 08:48:10 +00:00
2023-07-30 09:51:13 +00:00
if ( \in_array ( $providerPullRequestId , $providerPullRequestIds )) {
$providerPullRequestIds = \array_diff ( $providerPullRequestIds , [ $providerPullRequestId ]);
$repository = $repository -> setAttribute ( 'providerPullRequestIds' , $providerPullRequestIds );
2024-12-12 10:30:26 +00:00
$repository = Authorization :: skip ( fn () => $dbForPlatform -> updateDocument ( 'repositories' , $repository -> getId (), $repository ));
2023-06-28 08:48:10 +00:00
}
}
}
2023-05-22 10:58:13 +00:00
}
}
$response -> json ( $parsedPayload );
}
);
2024-10-08 07:54:40 +00:00
App :: get ( '/v1/vcs/installations' )
2023-05-22 10:58:13 +00:00
-> desc ( 'List installations' )
2023-08-30 18:44:33 +00:00
-> groups ([ 'api' , 'vcs' ])
-> label ( 'scope' , 'vcs.read' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'vcs' ,
2025-03-31 05:48:17 +00:00
group : 'installations' ,
2025-01-17 04:31:39 +00:00
name : 'listInstallations' ,
description : '/docs/references/vcs/list-installations.md' ,
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_INSTALLATION_LIST ,
)
]
))
2023-05-22 10:58:13 +00:00
-> param ( 'queries' , [], new Installations (), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode ( ', ' , Installations :: ALLOWED_ATTRIBUTES ), true )
-> param ( 'search' , '' , new Text ( 256 ), 'Search term to filter your list results. Max length: 256 chars.' , true )
-> inject ( 'response' )
-> inject ( 'project' )
2023-06-06 07:50:52 +00:00
-> inject ( 'dbForProject' )
2024-12-12 10:30:26 +00:00
-> inject ( 'dbForPlatform' )
-> action ( function ( array $queries , string $search , Response $response , Document $project , Database $dbForProject , Database $dbForPlatform ) {
2024-02-12 16:02:04 +00:00
try {
$queries = Query :: parseQueries ( $queries );
} catch ( QueryException $e ) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $e -> getMessage ());
}
2023-05-22 10:58:13 +00:00
2025-05-26 05:42:11 +00:00
$queries [] = Query :: equal ( 'projectInternalId' , [ $project -> getSequence ()]);
2023-05-22 10:58:13 +00:00
if ( ! empty ( $search )) {
$queries [] = Query :: search ( 'search' , $search );
}
2024-02-12 09:55:45 +00:00
/**
2024-02-12 10:03:31 +00:00
* Get cursor document if there was a cursor query , we use array_filter and reset for reference $cursor to $queries
2024-02-12 09:55:45 +00:00
*/
$cursor = \array_filter ( $queries , function ( $query ) {
return \in_array ( $query -> getMethod (), [ Query :: TYPE_CURSOR_AFTER , Query :: TYPE_CURSOR_BEFORE ]);
});
2023-05-22 10:58:13 +00:00
$cursor = reset ( $cursor );
if ( $cursor ) {
/** @var Query $cursor */
2024-10-17 05:41:24 +00:00
$validator = new Cursor ();
if ( ! $validator -> isValid ( $cursor )) {
throw new Exception ( Exception :: GENERAL_QUERY_INVALID , $validator -> getDescription ());
}
2023-07-30 09:51:13 +00:00
$installationId = $cursor -> getValue ();
2024-12-12 10:30:26 +00:00
$cursorDocument = $dbForPlatform -> getDocument ( 'installations' , $installationId );
2023-05-22 10:58:13 +00:00
if ( $cursorDocument -> isEmpty ()) {
2023-07-30 09:51:13 +00:00
throw new Exception ( Exception :: GENERAL_CURSOR_NOT_FOUND , " Installation ' { $installationId } ' for the 'cursor' value not found. " );
2023-05-22 10:58:13 +00:00
}
$cursor -> setValue ( $cursorDocument );
}
$filterQueries = Query :: groupByType ( $queries )[ 'filters' ];
2025-04-16 12:23:39 +00:00
try {
$results = $dbForPlatform -> find ( 'installations' , $queries );
2025-04-17 04:46:26 +00:00
$total = $dbForPlatform -> count ( 'installations' , $filterQueries , APP_LIMIT_COUNT );
2025-04-16 12:23:39 +00:00
} catch ( OrderException $e ) {
2025-04-17 04:46:26 +00:00
throw new Exception ( Exception :: DATABASE_QUERY_ORDER_NULL , " The order attribute ' { $e -> getAttribute () } ' had a null value. Cursor pagination requires all documents order attribute values are non-null. " );
2025-04-16 12:23:39 +00:00
}
2023-06-06 07:50:52 +00:00
2023-05-22 10:58:13 +00:00
$response -> dynamic ( new Document ([
2023-06-06 07:50:52 +00:00
'installations' => $results ,
'total' => $total ,
2023-05-22 10:58:13 +00:00
]), Response :: MODEL_INSTALLATION_LIST );
});
2024-10-08 07:54:40 +00:00
App :: get ( '/v1/vcs/installations/:installationId' )
2023-06-13 11:13:02 +00:00
-> desc ( 'Get installation' )
2023-08-30 18:44:33 +00:00
-> groups ([ 'api' , 'vcs' ])
-> label ( 'scope' , 'vcs.read' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'vcs' ,
2025-03-31 05:48:17 +00:00
group : 'installations' ,
2025-01-17 04:31:39 +00:00
name : 'getInstallation' ,
description : '/docs/references/vcs/get-installation.md' ,
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_OK ,
model : Response :: MODEL_INSTALLATION ,
)
]
))
2023-06-07 15:50:32 +00:00
-> param ( 'installationId' , '' , new Text ( 256 ), 'Installation Id' )
-> inject ( 'response' )
-> inject ( 'project' )
2024-12-12 10:30:26 +00:00
-> inject ( 'dbForPlatform' )
-> action ( function ( string $installationId , Response $response , Document $project , Database $dbForPlatform ) {
$installation = $dbForPlatform -> getDocument ( 'installations' , $installationId );
2023-06-07 15:50:32 +00:00
if ( $installation === false || $installation -> isEmpty ()) {
throw new Exception ( Exception :: INSTALLATION_NOT_FOUND );
}
2025-05-26 05:42:11 +00:00
if ( $installation -> getAttribute ( 'projectInternalId' ) !== $project -> getSequence ()) {
2023-06-07 15:50:32 +00:00
throw new Exception ( Exception :: INSTALLATION_NOT_FOUND );
}
$response -> dynamic ( $installation , Response :: MODEL_INSTALLATION );
});
2024-10-08 07:54:40 +00:00
App :: delete ( '/v1/vcs/installations/:installationId' )
-> desc ( 'Delete installation' )
2023-08-30 18:44:33 +00:00
-> groups ([ 'api' , 'vcs' ])
-> label ( 'scope' , 'vcs.write' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'vcs' ,
2025-03-31 05:48:17 +00:00
group : 'installations' ,
2025-01-17 04:31:39 +00:00
name : 'deleteInstallation' ,
description : '/docs/references/vcs/delete-installation.md' ,
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_NOCONTENT ,
model : Response :: MODEL_NONE ,
)
],
contentType : ContentType :: NONE
))
2023-05-22 10:58:13 +00:00
-> param ( 'installationId' , '' , new Text ( 256 ), 'Installation Id' )
-> inject ( 'response' )
-> inject ( 'project' )
2024-12-12 10:30:26 +00:00
-> inject ( 'dbForPlatform' )
2023-11-15 06:11:42 +00:00
-> inject ( 'queueForDeletes' )
2024-12-12 10:30:26 +00:00
-> action ( function ( string $installationId , Response $response , Document $project , Database $dbForPlatform , Delete $queueForDeletes ) {
$installation = $dbForPlatform -> getDocument ( 'installations' , $installationId );
2023-05-22 10:58:13 +00:00
if ( $installation -> isEmpty ()) {
throw new Exception ( Exception :: INSTALLATION_NOT_FOUND );
}
2024-12-12 10:30:26 +00:00
if ( ! $dbForPlatform -> deleteDocument ( 'installations' , $installation -> getId ())) {
2023-05-22 10:58:13 +00:00
throw new Exception ( Exception :: GENERAL_SERVER_ERROR , 'Failed to remove installation from DB' );
}
2023-11-15 06:11:42 +00:00
$queueForDeletes
2023-05-22 10:58:13 +00:00
-> setType ( DELETE_TYPE_DOCUMENT )
-> setDocument ( $installation );
$response -> noContent ();
});
2023-06-13 18:44:44 +00:00
2024-10-08 07:54:40 +00:00
App :: patch ( '/v1/vcs/github/installations/:installationId/repositories/:repositoryId' )
2025-04-14 18:40:48 +00:00
-> desc ( 'Update external deployment (authorize)' )
2023-06-28 08:48:10 +00:00
-> groups ([ 'api' , 'vcs' ])
2023-08-30 18:44:33 +00:00
-> label ( 'scope' , 'vcs.write' )
2025-01-17 04:31:39 +00:00
-> label ( 'sdk' , new Method (
namespace : 'vcs' ,
2025-03-31 05:48:17 +00:00
group : 'repositories' ,
2025-01-17 04:31:39 +00:00
name : 'updateExternalDeployments' ,
2025-01-17 07:44:25 +00:00
description : '/docs/references/vcs/update-external-deployments.md' ,
2025-01-17 04:31:39 +00:00
auth : [ AuthType :: ADMIN ],
responses : [
new SDKResponse (
code : Response :: STATUS_CODE_NOCONTENT ,
model : Response :: MODEL_NONE ,
)
]
))
2023-06-28 08:48:10 +00:00
-> param ( 'installationId' , '' , new Text ( 256 ), 'Installation Id' )
2023-07-30 09:51:13 +00:00
-> param ( 'repositoryId' , '' , new Text ( 256 ), 'VCS Repository Id' )
-> param ( 'providerPullRequestId' , '' , new Text ( 256 ), 'GitHub Pull Request Id' )
2023-06-28 08:48:10 +00:00
-> inject ( 'gitHub' )
-> inject ( 'request' )
-> inject ( 'response' )
-> inject ( 'project' )
2024-12-12 10:30:26 +00:00
-> inject ( 'dbForPlatform' )
2023-06-28 08:48:10 +00:00
-> inject ( 'getProjectDB' )
2023-10-09 10:28:06 +00:00
-> inject ( 'queueForBuilds' )
2024-12-12 10:30:26 +00:00
-> action ( function ( string $installationId , string $repositoryId , string $providerPullRequestId , GitHub $github , Request $request , Response $response , Document $project , Database $dbForPlatform , callable $getProjectDB , Build $queueForBuilds ) use ( $createGitDeployments ) {
$installation = $dbForPlatform -> getDocument ( 'installations' , $installationId );
2023-06-28 08:48:10 +00:00
if ( $installation -> isEmpty ()) {
throw new Exception ( Exception :: INSTALLATION_NOT_FOUND );
}
2024-12-12 10:30:26 +00:00
$repository = Authorization :: skip ( fn () => $dbForPlatform -> getDocument ( 'repositories' , $repositoryId , [
2025-05-26 05:42:11 +00:00
Query :: equal ( 'projectInternalId' , [ $project -> getSequence ()])
2023-11-14 15:50:59 +00:00
]));
2023-06-28 08:48:10 +00:00
2023-07-30 09:51:13 +00:00
if ( $repository -> isEmpty ()) {
2023-07-31 06:47:47 +00:00
throw new Exception ( Exception :: REPOSITORY_NOT_FOUND );
2023-06-28 08:48:10 +00:00
}
2023-07-30 09:51:13 +00:00
if ( \in_array ( $providerPullRequestId , $repository -> getAttribute ( 'providerPullRequestIds' , []))) {
2023-07-31 06:47:47 +00:00
throw new Exception ( Exception :: PROVIDER_CONTRIBUTION_CONFLICT );
2023-06-28 08:48:10 +00:00
}
2023-07-30 09:51:13 +00:00
$providerPullRequestIds = \array_unique ( \array_merge ( $repository -> getAttribute ( 'providerPullRequestIds' , []), [ $providerPullRequestId ]));
$repository = $repository -> setAttribute ( 'providerPullRequestIds' , $providerPullRequestIds );
2023-06-28 08:48:10 +00:00
// TODO: Delete from array when PR is closed
2024-12-12 10:30:26 +00:00
$repository = Authorization :: skip ( fn () => $dbForPlatform -> updateDocument ( 'repositories' , $repository -> getId (), $repository ));
2023-06-28 08:48:10 +00:00
2024-04-01 11:02:47 +00:00
$privateKey = System :: getEnv ( '_APP_VCS_GITHUB_PRIVATE_KEY' );
$githubAppId = System :: getEnv ( '_APP_VCS_GITHUB_APP_ID' );
2023-07-30 09:51:13 +00:00
$providerInstallationId = $installation -> getAttribute ( 'providerInstallationId' );
2023-08-09 17:35:23 +00:00
$github -> initializeVariables ( $providerInstallationId , $privateKey , $githubAppId );
2023-06-28 08:48:10 +00:00
2023-07-30 09:51:13 +00:00
$repositories = [ $repository ];
$providerRepositoryId = $repository -> getAttribute ( 'providerRepositoryId' );
2023-06-28 08:48:10 +00:00
2023-07-30 09:51:13 +00:00
$owner = $github -> getOwnerName ( $providerInstallationId );
2023-10-27 14:08:33 +00:00
try {
$repositoryName = $github -> getRepositoryName ( $providerRepositoryId ) ? ? '' ;
if ( empty ( $repositoryName )) {
throw new Exception ( Exception :: PROVIDER_REPOSITORY_NOT_FOUND );
}
} catch ( RepositoryNotFound $e ) {
throw new Exception ( Exception :: PROVIDER_REPOSITORY_NOT_FOUND );
}
2023-07-30 09:51:13 +00:00
$pullRequestResponse = $github -> getPullRequest ( $owner , $repositoryName , $providerPullRequestId );
2023-06-28 08:48:10 +00:00
2023-07-30 09:51:13 +00:00
$providerBranch = \explode ( ':' , $pullRequestResponse [ 'head' ][ 'label' ])[ 1 ] ? ? '' ;
$providerCommitHash = $pullRequestResponse [ 'head' ][ 'sha' ] ? ? '' ;
2023-06-28 08:48:10 +00:00
2024-12-12 10:30:26 +00:00
$createGitDeployments ( $github , $providerInstallationId , $repositories , $providerBranch , $providerCommitHash , $providerPullRequestId , true , $dbForPlatform , $queueForBuilds , $getProjectDB , $request );
2023-06-28 08:48:10 +00:00
$response -> noContent ();
});