1
0
Fork 0
mirror of synced 2024-07-02 21:20:58 +12:00
appwrite/app/controllers/api/vcs.php

877 lines
39 KiB
PHP
Raw Normal View History

<?php
2023-06-18 23:38:37 +12:00
use Appwrite\Auth\OAuth2\Github as OAuth2Github;
use Swoole\Coroutine as Co;
use Utopia\App;
use Appwrite\Event\Build;
use Appwrite\Event\Delete;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Utopia\Validator\Text;
use Utopia\VCS\Adapter\Git\GitHub;
use Appwrite\Extend\Exception;
use Appwrite\Network\Validator\Host;
use Appwrite\Utopia\Database\Validator\Queries\Installations;
use Appwrite\Vcs\Comment;
2023-06-18 23:38:37 +12:00
use Utopia\Database\DateTime;
use Utopia\Database\Query;
2023-05-23 01:02:55 +12:00
use Utopia\Database\ID;
use Utopia\Database\Permission;
use Utopia\Database\Role;
use Utopia\Database\Validator\Authorization;
2023-06-14 06:44:44 +12:00
use Utopia\Detector\Adapter\CPP;
use Utopia\Detector\Adapter\Dart;
use Utopia\Detector\Adapter\Deno;
use Utopia\Detector\Adapter\Dotnet;
use Utopia\Detector\Adapter\Java;
use Utopia\Detector\Adapter\JavaScript;
use Utopia\Detector\Adapter\PHP;
use Utopia\Detector\Adapter\Python;
use Utopia\Detector\Adapter\Ruby;
use Utopia\Detector\Adapter\Swift;
use Utopia\Detector\Detector;
2023-06-18 23:38:37 +12:00
use Utopia\Validator\Boolean;
use function Swoole\Coroutine\batch;
App::get('/v1/vcs/github/installations')
->desc('Install GitHub App')
->groups(['api', 'vcs'])
->label('scope', 'public')
->label('origin', '*')
->label('sdk.auth', [])
->label('sdk.namespace', 'vcs')
->label('sdk.method', 'createGitHubInstallation')
->label('sdk.description', '')
->label('sdk.response.code', Response::STATUS_CODE_MOVED_PERMANENTLY)
->label('sdk.response.type', Response::CONTENT_TYPE_HTML)
->label('sdk.methodType', 'webAuth')
->param('redirect', '', fn ($clients) => new Host($clients), 'URL to redirect back to your Git authorization. Only console hostnames are allowed.', true, ['clients'])
->inject('response')
->inject('project')
->action(function (string $redirect, Response $response, Document $project) {
$projectId = $project->getId();
$state = \json_encode([
'projectId' => $projectId,
'redirect' => $redirect
]);
$appName = App::getEnv('VCS_GITHUB_APP_NAME');
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
->redirect("https://github.com/apps/$appName/installations/new?" . \http_build_query([
'state' => $state
]));
});
2023-06-18 23:38:37 +12:00
App::get('/v1/vcs/github/redirect')
->desc('Capture installation and authorization from GitHub App')
->groups(['api', 'vcs'])
->label('scope', 'public')
->param('installation_id', '', new Text(256), 'GitHub installation ID')
->param('setup_action', '', new Text(256), 'GitHub setup actuon type')
->param('state', '', new Text(2048), 'GitHub state. Contains info sent when starting authorization flow.')
2023-06-18 23:38:37 +12:00
->param('code', '', new Text(2048), 'OAuth2 code.', true)
2023-05-26 20:44:08 +12:00
->inject('gitHub')
2023-06-18 23:38:37 +12:00
->inject('user')
->inject('project')
->inject('request')
->inject('response')
->inject('dbForConsole')
2023-06-18 23:38:37 +12:00
->action(function (string $installationId, string $setupAction, string $state, string $code, GitHub $github, Document $user, Document $project, Request $request, Response $response, Database $dbForConsole) {
$state = \json_decode($state, true);
$redirect = $state['redirect'] ?? '';
2023-06-18 23:38:37 +12:00
$projectId = $state['projectId'] ?? '';
2023-06-18 23:38:37 +12:00
$project = $dbForConsole->getDocument('projects', $projectId);
if (empty($redirect)) {
$redirect = $request->getProtocol() . '://' . $request->getHostname() . "/console/project-$projectId/settings/git-installations";
}
if ($project->isEmpty()) {
2023-06-06 19:50:52 +12:00
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
->redirect($redirect);
}
2023-06-18 23:38:37 +12:00
$personalSlug = '';
2023-06-18 23:38:37 +12:00
// OAuth Authroization
if (!empty($code)) {
$oauth2 = new OAuth2Github(App::getEnv('VCS_GITHUB_CLIENT_ID', ''), App::getEnv('VCS_GITHUB_CLIENT_SECRET', ''), "");
$accessToken = $oauth2->getAccessToken($code);
$refreshToken = $oauth2->getRefreshToken($code);
$accessTokenExpiry = $oauth2->getAccessTokenExpiry($code);
$personalSlug = $oauth2->getUserSlug($accessToken);
2023-06-18 23:38:37 +12:00
$user = $user
->setAttribute('vcsGithubAccessToken', $accessToken)
->setAttribute('vcsGithubRefreshToken', $refreshToken)
->setAttribute('vcsGithubAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$accessTokenExpiry));
$dbForConsole->updateDocument('users', $user->getId(), $user);
}
2023-06-18 23:38:37 +12:00
// Create / Update installation
if (!empty($installationId)) {
$privateKey = App::getEnv('VCS_GITHUB_PRIVATE_KEY');
$githubAppId = App::getEnv('VCS_GITHUB_APP_ID');
$github->initialiseVariables($installationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($installationId);
$projectInternalId = $project->getInternalId();
$vcsInstallation = $dbForConsole->findOne('vcsInstallations', [
Query::equal('installationId', [$installationId]),
Query::equal('projectInternalId', [$projectInternalId])
]);
2023-06-18 23:38:37 +12:00
if ($vcsInstallation === false || $vcsInstallation->isEmpty()) {
$teamId = $project->getAttribute('teamId', '');
$vcsInstallation = new Document([
'$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')),
],
'installationId' => $installationId,
'projectId' => $projectId,
'projectInternalId' => $projectInternalId,
'provider' => 'github',
'organization' => $owner,
'personal' => $personalSlug === $owner
]);
$vcsInstallation = $dbForConsole->createDocument('vcsInstallations', $vcsInstallation);
} else {
$vcsInstallation = $vcsInstallation->setAttribute('organization', $owner);
$vcsInstallation = $vcsInstallation->setAttribute('personal', $personalSlug === $owner);
$vcsInstallation = $dbForConsole->updateDocument('vcsInstallations', $vcsInstallation->getId(), $vcsInstallation);
}
}
$response
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
->redirect($redirect);
});
App::get('/v1/vcs/github/installations/:installationId/repositories')
->desc('List repositories')
->groups(['api', 'vcs'])
->label('scope', 'public')
->label('sdk.namespace', 'vcs')
->label('sdk.method', 'listRepositories')
->label('sdk.description', '')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_REPOSITORY_LIST)
->param('installationId', '', new Text(256), 'Installation Id')
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
2023-05-26 20:44:08 +12:00
->inject('gitHub')
->inject('response')
->inject('project')
->inject('dbForConsole')
2023-05-26 20:44:08 +12:00
->action(function (string $vcsInstallationId, string $search, GitHub $github, Response $response, Document $project, Database $dbForConsole) {
if (empty($search)) {
$search = "";
}
$installation = $dbForConsole->getDocument('vcsInstallations', $vcsInstallationId, [
Query::equal('projectInternalId', [$project->getInternalId()])
]);
if ($installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
$installationId = $installation->getAttribute('installationId');
$privateKey = App::getEnv('VCS_GITHUB_PRIVATE_KEY');
$githubAppId = App::getEnv('VCS_GITHUB_APP_ID');
$github->initialiseVariables($installationId, $privateKey, $githubAppId);
$page = 1;
$perPage = 100;
$loadPage = function ($page) use ($github, $perPage) {
$repos = $github->listRepositoriesForGitHubApp($page, $perPage);
return $repos;
};
$reposPages = batch([
function () use ($loadPage) {
return $loadPage(1);
},
function () use ($loadPage) {
return $loadPage(2);
},
function () use ($loadPage) {
return $loadPage(3);
}
]);
$page += 3;
$repos = [];
foreach ($reposPages as $reposPage) {
$repos = \array_merge($repos, $reposPage);
}
// All 3 pages were full, we paginate more
2023-06-17 02:43:37 +12:00
if (\count($repos) === 3 * $perPage) {
do {
$reposPage = $loadPage($page);
$repos = array_merge($repos, $reposPage);
$page++;
} while (\count($reposPage) === $perPage);
}
// Filter repositories based on search parameter
if (!empty($search)) {
$repos = array_filter($repos, function ($repo) use ($search) {
return \str_contains(\strtolower($repo['name']), \strtolower($search));
});
}
// Sort repositories by last modified date in descending order
usort($repos, function ($repo1, $repo2) {
return \strtotime($repo2['pushed_at']) - \strtotime($repo1['pushed_at']);
});
// Limit the maximum results to 5
$repos = \array_slice($repos, 0, 5);
2023-05-27 23:55:34 +12:00
$repos = \array_map(function ($repo) {
$repo['id'] = \strval($repo['id']);
$repo['pushedAt'] = $repo['pushed_at'];
2023-05-27 23:55:34 +12:00
return new Document($repo);
}, $repos);
$response->dynamic(new Document([
'repositories' => $repos,
'total' => \count($repos),
]), Response::MODEL_REPOSITORY_LIST);
});
2023-06-18 23:38:37 +12:00
App::post('/v1/vcs/github/installations/:installationId/repositories')
->desc('Create repository')
->groups(['api', 'vcs'])
->label('scope', 'public')
->label('sdk.namespace', 'vcs')
->label('sdk.method', 'createRepository')
->label('sdk.description', '')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_REPOSITORY)
->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')
->inject('dbForConsole')
->action(function (string $vcsInstallationId, string $name, bool $private, GitHub $github, Document $user, Response $response, Document $project, Database $dbForConsole) {
$installation = $dbForConsole->getDocument('vcsInstallations', $vcsInstallationId, [
Query::equal('projectInternalId', [$project->getInternalId()])
]);
if ($installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
if ($installation->getAttribute('personal', false) === true) {
$oauth2 = new OAuth2Github(App::getEnv('VCS_GITHUB_CLIENT_ID', ''), App::getEnv('VCS_GITHUB_CLIENT_SECRET', ''), "");
$accessToken = $user->getAttribute('vcsGithubAccessToken');
$refreshToken = $user->getAttribute('vcsGithubRefreshToken');
$accessTokenExpiry = $user->getAttribute('vcsGithubAccessTokenExpiry');
$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.");
}
$user = $user
->setAttribute('vcsGithubAccessToken', $accessToken)
->setAttribute('vcsGithubRefreshToken', $refreshToken)
->setAttribute('vcsGithubAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry('')));
$dbForConsole->updateDocument('users', $user->getId(), $user);
}
$repository = $oauth2->createRepository($accessToken, $name, $private);
} else {
$installationId = $installation->getAttribute('installationId');
$privateKey = App::getEnv('VCS_GITHUB_PRIVATE_KEY');
$githubAppId = App::getEnv('VCS_GITHUB_APP_ID');
$github->initialiseVariables($installationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($installationId);
$repository = $github->createRepository($owner, $name, $private);
}
$repository['id'] = \strval($repository['id']);
$repository['pushedAt'] = $repository['pushed_at'];
$response->dynamic(new Document($repository), Response::MODEL_REPOSITORY);
});
App::get('/v1/vcs/github/installations/:installationId/repositories/:repositoryId')
->desc('Get repository')
->groups(['api', 'vcs'])
->label('scope', 'public')
->label('sdk.namespace', 'vcs')
->label('sdk.method', 'getRepository')
->label('sdk.description', '')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_REPOSITORY)
->param('installationId', '', new Text(256), 'Installation Id')
->param('repositoryId', '', new Text(256), 'Repository Id')
->inject('gitHub')
->inject('response')
->inject('project')
->inject('dbForConsole')
->action(function (string $vcsInstallationId, string $repositoryId, GitHub $github, Response $response, Document $project, Database $dbForConsole) {
$installation = $dbForConsole->getDocument('vcsInstallations', $vcsInstallationId, [
Query::equal('projectInternalId', [$project->getInternalId()])
]);
if ($installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
$installationId = $installation->getAttribute('installationId');
$privateKey = App::getEnv('VCS_GITHUB_PRIVATE_KEY');
$githubAppId = App::getEnv('VCS_GITHUB_APP_ID');
$github->initialiseVariables($installationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($installationId);
$repositoryName = $github->getRepositoryName($repositoryId);
if (empty($repositoryName)) {
throw new Exception(Exception::REPOSITORY_NOT_FOUND);
}
$repository = $github->getRepository($owner, $repositoryName);
$repository['id'] = \strval($repository['id']);
$repository['pushedAt'] = $repository['pushed_at'];
$response->dynamic(new Document($repository), Response::MODEL_REPOSITORY);
});
App::get('/v1/vcs/github/installations/:installationId/repositories/:repositoryId/branches')
->desc('List Repository Branches')
->groups(['api', 'vcs'])
->label('scope', 'public')
->label('sdk.namespace', 'vcs')
->label('sdk.method', 'listRepositoryBranches')
->label('sdk.description', '')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_BRANCH_LIST)
->param('installationId', '', new Text(256), 'Installation Id')
->param('repositoryId', '', new Text(256), 'Repository Id')
2023-05-26 20:44:08 +12:00
->inject('gitHub')
->inject('response')
->inject('project')
->inject('dbForConsole')
2023-05-26 20:44:08 +12:00
->action(function (string $vcsInstallationId, string $repositoryId, GitHub $github, Response $response, Document $project, Database $dbForConsole) {
$installation = $dbForConsole->getDocument('vcsInstallations', $vcsInstallationId, [
Query::equal('projectInternalId', [$project->getInternalId()])
]);
if ($installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
$installationId = $installation->getAttribute('installationId');
$privateKey = App::getEnv('VCS_GITHUB_PRIVATE_KEY');
$githubAppId = App::getEnv('VCS_GITHUB_APP_ID');
$github->initialiseVariables($installationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($installationId);
$repoName = $github->getRepositoryName($repositoryId);
$branches = $github->listBranches($owner, $repoName);
$response->dynamic(new Document([
'branches' => \array_map(function ($branch) {
return ['name' => $branch];
}, $branches),
'total' => \count($branches),
]), Response::MODEL_BRANCH_LIST);
});
2023-06-17 22:07:30 +12:00
$createGitDeployments = function (GitHub $github, string $installationId, string $repositoryId, array $vcsRepos, string $branchName, string $SHA, string $commentId, Database $dbForConsole, callable $getProjectDB, Request $request) {
2023-05-25 17:30:52 +12:00
foreach ($vcsRepos as $resource) {
$resourceType = $resource->getAttribute('resourceType');
if ($resourceType === "function") {
$projectId = $resource->getAttribute('projectId');
$project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId));
$dbForProject = $getProjectDB($project);
$functionId = $resource->getAttribute('resourceId');
2023-06-09 23:34:46 +12:00
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId));
2023-05-25 17:30:52 +12:00
$deploymentId = ID::unique();
$vcsRepoId = $resource->getId();
$vcsRepoInternalId = $resource->getInternalId();
$vcsInstallationId = $resource->getAttribute('vcsInstallationId');
$vcsInstallationInternalId = $resource->getAttribute('vcsInstallationInternalId');
2023-06-09 23:34:46 +12:00
$productionBranch = $function->getAttribute('vcsBranch');
2023-05-25 17:30:52 +12:00
$activate = false;
if ($branchName == $productionBranch) {
$activate = true;
}
$latestDeployment = Authorization::skip(fn () => $dbForProject->findOne('deployments', [
2023-06-17 22:07:30 +12:00
Query::equal('vcsRepositoryId', [$repositoryId]),
2023-06-09 23:34:46 +12:00
Query::equal('vcsBranch', [$branchName]),
Query::equal('resourceType', ['functions']),
Query::orderDesc('$createdAt'),
]));
$latestCommentId = $commentId ?? '';
if (empty($latestCommentId)) {
// Empty comment ID is push event. We try to take ID from last deplyoment
if ($latestDeployment !== false && !$latestDeployment->isEmpty()) {
$latestCommentId = $latestDeployment->getAttribute('vcsCommentId', '');
}
} else {
// Known comment ID is pull request event. If deployment exists already, we skip
if ($latestDeployment !== false && !$latestDeployment->isEmpty()) {
$latestDeployment->setAttribute('vcsCommentId', $latestCommentId);
Authorization::skip(fn () => $dbForProject->updateDocument('deployments', $latestDeployment->getId(), $latestDeployment));
continue;
}
}
2023-06-17 22:07:30 +12:00
$owner = $github->getOwnerName($installationId);
$repositoryName = $github->getRepositoryName($repositoryId);
$comment = new Comment();
2023-06-18 23:38:37 +12:00
// TODO: Add all builds
2023-06-17 22:07:30 +12:00
$comment->addBuild($project, $function, 'waiting', $deploymentId);
if (empty($latestCommentId)) {
$pullRequest = $github->getBranchPullRequest($owner, $repositoryName, $branchName);
if (!empty($pullRequest)) {
$pullRequestNumber = \strval($pullRequest['number']);
$latestCommentId = $github->createComment($owner, $repositoryName, $pullRequestNumber, $comment->generateComment());
}
} else {
$latestCommentId = $github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment());
}
2023-05-25 17:30:52 +12:00
$deployment = $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'resourceId' => $functionId,
'resourceType' => 'functions',
'entrypoint' => $function->getAttribute('entrypoint'),
'installCommand' => $function->getAttribute('installCommand'),
'buildCommand' => $function->getAttribute('buildCommand'),
'type' => 'vcs',
'vcsInstallationId' => $vcsInstallationId,
'vcsInstallationInternalId' => $vcsInstallationInternalId,
2023-06-17 22:07:30 +12:00
'vcsRepositoryId' => $repositoryId,
'vcsRepositoryDocId' => $vcsRepoId,
'vcsRepositoryDocInternalId' => $vcsRepoInternalId,
'vcsCommentId' => \strval($latestCommentId),
'vcsBranch' => $branchName,
2023-05-25 17:30:52 +12:00
'search' => implode(' ', [$deploymentId, $function->getAttribute('entrypoint')]),
'activate' => $activate,
]));
// TODO: Figure out port
$targetUrl = $request->getProtocol() . '://' . $request->getHostname() . ":3000/console/project-$projectId/functions/function-$functionId";
if (!empty($SHA) && $function->getAttribute('vcsSilentMode', false) === false) {
$functionName = $function->getAttribute('name');
$projectName = $project->getAttribute('name');
$name = "{$functionName} ({$projectName})";
$message = 'Starting...';
$repositoryId = $resource->getAttribute('repositoryId');
$repositoryName = $github->getRepositoryName($repositoryId);
$owner = $github->getOwnerName($installationId);
$github->updateCommitStatus($repositoryName, $SHA, $owner, 'pending', $message, $targetUrl, $name);
}
2023-05-25 17:30:52 +12:00
$buildEvent = new Build();
$buildEvent
->setType(BUILD_TYPE_DEPLOYMENT)
->setResource($function)
->setDeployment($deployment)
->setTargetUrl($targetUrl)
->setSHA($SHA)
->setProject($project)
->trigger();
//TODO: Add event?
}
}
};
App::post('/v1/vcs/github/incomingwebhook')
->desc('Captures GitHub Webhook Events')
->groups(['api', 'vcs'])
->label('scope', 'public')
2023-05-26 20:44:08 +12:00
->inject('gitHub')
->inject('request')
->inject('response')
->inject('dbForConsole')
2023-05-23 16:37:25 +12:00
->inject('getProjectDB')
->action(
2023-05-26 20:44:08 +12:00
function (GitHub $github, Request $request, Response $response, Database $dbForConsole, callable $getProjectDB) use ($createGitDeployments) {
2023-06-15 22:37:28 +12:00
$signature = $request->getHeader('x-hub-signature-256', '');
$payload = $request->getRawPayload();
2023-06-15 22:37:28 +12:00
$signatureKey = App::getEnv('VCS_GITHUB_WEBHOOK_SECRET', '');
2023-06-15 22:38:03 +12:00
2023-06-15 22:37:28 +12:00
$valid = $github->validateWebhook($payload, $signature, $signatureKey);
2023-06-15 22:38:03 +12:00
if (!$valid) {
2023-06-15 22:37:28 +12:00
throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN, "Invalid webhook signature.");
}
$event = $request->getHeader('x-github-event', '');
$privateKey = App::getEnv('VCS_GITHUB_PRIVATE_KEY');
$githubAppId = App::getEnv('VCS_GITHUB_APP_ID');
$parsedPayload = $github->parseWebhookEventPayload($event, $payload);
if ($event == $github::EVENT_PUSH) {
$branchName = $parsedPayload["branch"];
$repositoryId = $parsedPayload["repositoryId"];
$installationId = $parsedPayload["installationId"];
$SHA = $parsedPayload["SHA"];
$owner = $parsedPayload["owner"];
$github->initialiseVariables($installationId, $privateKey, $githubAppId);
//find functionId from functions table
$vcsRepos = $dbForConsole->find('vcsRepos', [
Query::equal('repositoryId', [$repositoryId]),
Query::limit(100),
]);
2023-06-17 22:07:30 +12:00
$createGitDeployments($github, $installationId, $repositoryId, $vcsRepos, $branchName, $SHA, '', $dbForConsole, $getProjectDB, $request);
} elseif ($event == $github::EVENT_INSTALLATION) {
if ($parsedPayload["action"] == "deleted") {
// TODO: Use worker for this job instead (update function as well)
$installationId = $parsedPayload["installationId"];
$vcsInstallations = $dbForConsole->find('vcsInstallations', [
Query::equal('installationId', [$installationId]),
Query::limit(1000)
]);
foreach ($vcsInstallations as $installation) {
$vcsRepos = $dbForConsole->find('vcsRepos', [
Query::equal('vcsInstallationId', [$installation->getId()]),
Query::limit(1000)
]);
foreach ($vcsRepos as $repo) {
$dbForConsole->deleteDocument('vcsRepos', $repo->getId());
}
$dbForConsole->deleteDocument('vcsInstallations', $installation->getId());
}
}
} elseif ($event == $github::EVENT_PULL_REQUEST) {
if ($parsedPayload["action"] == "opened" or $parsedPayload["action"] == "reopened") {
$branchName = $parsedPayload["branch"];
$repositoryId = $parsedPayload["repositoryId"];
$installationId = $parsedPayload["installationId"];
$pullRequestNumber = $parsedPayload["pullRequestNumber"];
$repositoryName = $parsedPayload["repositoryName"];
$owner = $parsedPayload["owner"];
$github->initialiseVariables($installationId, $privateKey, $githubAppId);
$vcsRepos = $dbForConsole->find('vcsRepos', [
Query::equal('repositoryId', [$repositoryId]),
Query::orderDesc('$createdAt')
]);
if (\count($vcsRepos) !== 0) {
$comment = new Comment();
foreach ($vcsRepos as $vcsRepo) {
$projectId = $vcsRepo->getAttribute('projectId');
$project = Authorization::skip(fn () => $dbForConsole->getDocument('projects', $projectId));
$dbForProject = $getProjectDB($project);
$vcsRepoId = $vcsRepo->getId();
$deployment = Authorization::skip(fn () => $dbForProject->findOne('deployments', [
2023-06-17 22:07:30 +12:00
Query::equal('vcsRepositoryDocId', [$vcsRepoId]),
Query::equal('vcsBranch', [$branchName]),
Query::equal('resourceType', ['functions']),
Query::orderDesc('$createdAt'),
]));
if (!$deployment || $deployment->isEmpty()) {
$function = Authorization::skip(fn () => $dbForProject->findOne('functions', [
2023-06-17 22:07:30 +12:00
Query::equal('vcsRepositoryDocId', [$vcsRepoId]),
Query::orderDesc('$createdAt'),
]));
$build = new Document([]);
} else {
$function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $deployment->getAttribute('resourceId', '')));
$build = Authorization::skip(fn () => $dbForProject->getDocument('builds', $deployment->getAttribute('buildId', '')));
}
if (!$function || $function->isEmpty()) {
continue;
}
$status = !$build || $build->isEmpty() ? 'waiting' : $build->getAttribute('status', 'waiting');
$deploymentId = !$build || $build->isEmpty() ? '' : $build->getAttribute('deploymentId', '');
$comment->addBuild($project, $function, $status, $deploymentId);
}
$commentId = '';
if (!$comment->isEmpty() && $function->getAttribute('vcsSilentMode', false) === false) {
$commentId = $github->createComment($owner, $repositoryName, $pullRequestNumber, $comment->generateComment());
}
2023-05-25 17:30:52 +12:00
2023-06-17 22:07:30 +12:00
$createGitDeployments($github, $installationId, $repositoryId, $vcsRepos, $branchName, '', $commentId, $dbForConsole, $getProjectDB, $request);
}
}
}
$response->json($parsedPayload);
}
);
App::get('/v1/vcs/installations')
->groups(['api', 'vcs'])
->desc('List installations')
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'vcs')
->label('sdk.method', 'listInstallations')
->label('sdk.description', '/docs/references/vcs/list-installations.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_INSTALLATION_LIST)
->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 19:50:52 +12:00
->inject('dbForProject')
->inject('dbForConsole')
2023-06-06 19:50:52 +12:00
->action(function (array $queries, string $search, Response $response, Document $project, Database $dbForProject, Database $dbForConsole) {
$queries = Query::parseQueries($queries);
$queries[] = Query::equal('projectInternalId', [$project->getInternalId()]);
if (!empty($search)) {
$queries[] = Query::search('search', $search);
}
// Get cursor document if there was a cursor query
$cursor = Query::getByType($queries, Query::TYPE_CURSORAFTER, Query::TYPE_CURSORBEFORE);
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
$vcsInstallationId = $cursor->getValue();
$cursorDocument = $dbForConsole->getDocument('vcsInstallations', $vcsInstallationId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Installation '{$vcsInstallationId}' for the 'cursor' value not found.");
}
$cursor->setValue($cursorDocument);
}
$filterQueries = Query::groupByType($queries)['filters'];
$results = $dbForConsole->find('vcsInstallations', $queries);
$total = $dbForConsole->count('vcsInstallations', $filterQueries, APP_LIMIT_COUNT);
2023-06-06 19:50:52 +12:00
if (\count($results) > 0) {
$installationIds = \array_map(fn ($result) => $result->getId(), $results);
$functions = Authorization::skip(fn () => $dbForProject->find('functions', [
Query::equal('vcsInstallationId', \array_unique($installationIds)),
Query::limit(APP_LIMIT_SUBQUERY)
]));
foreach ($results as $result) {
$installationFunctions = \array_filter($functions, fn ($function) => $function->getAttribute('vcsInstallationId') === $result->getId());
$result->setAttribute('functions', $installationFunctions);
}
}
$response->dynamic(new Document([
2023-06-06 19:50:52 +12:00
'installations' => $results,
'total' => $total,
]), Response::MODEL_INSTALLATION_LIST);
});
App::get('/v1/vcs/installations/:installationId')
->groups(['api', 'vcs'])
2023-06-13 23:13:02 +12:00
->desc('Get installation')
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'vcs')
->label('sdk.method', 'getInstallation')
->label('sdk.description', '/docs/references/vcs/get-installation.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_INSTALLATION)
->param('installationId', '', new Text(256), 'Installation Id')
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('dbForConsole')
->action(function (string $installationId, Response $response, Document $project, Database $dbForProject, Database $dbForConsole) {
$installation = $dbForConsole->getDocument('vcsInstallations', $installationId);
if ($installation === false || $installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
if ($installation->getAttribute('projectInternalId') !== $project->getInternalId()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
$functions = Authorization::skip(fn () => $dbForProject->find('functions', [
Query::equal('vcsInstallationId', [$installation->getId()]),
Query::limit(APP_LIMIT_SUBQUERY)
]));
$installation->setAttribute('functions', $functions);
$response->dynamic($installation, Response::MODEL_INSTALLATION);
});
App::delete('/v1/vcs/installations/:installationId')
->groups(['api', 'vcs'])
->desc('Delete Installation')
->label('scope', 'public')
->label('sdk.auth', [APP_AUTH_TYPE_KEY])
->label('sdk.namespace', 'vcs')
->label('sdk.method', 'deleteInstallation')
->label('sdk.description', '/docs/references/vcs/delete-installation.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->param('installationId', '', new Text(256), 'Installation Id')
->inject('response')
->inject('project')
->inject('dbForConsole')
->inject('deletes')
->action(function (string $vcsInstallationId, Response $response, Document $project, Database $dbForConsole, Delete $deletes) {
$installation = $dbForConsole->getDocument('vcsInstallations', $vcsInstallationId, [
Query::equal('projectInternalId', [$project->getInternalId()])
]);
if ($installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
if (!$dbForConsole->deleteDocument('vcsInstallations', $installation->getId())) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove installation from DB');
}
$deletes
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($installation);
$response->noContent();
});
2023-06-14 06:44:44 +12:00
App::get('/v1/vcs/github/installations/:installationId/repositories/:repositoryId/detection')
->desc('Detect function settings like runtime for specified root directory')
->groups(['api', 'vcs'])
->label('scope', 'public')
->label('sdk.namespace', 'vcs')
->label('sdk.method', 'createRepositoryDetection')
->label('sdk.description', '')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DETECTION)
->param('installationId', '', new Text(256), 'Installation Id')
->param('repositoryId', '', new Text(256), 'Repository Id')
->param('rootDirectoryPath', '', new Text(256), 'Path to Root Directory', true)
->inject('gitHub')
->inject('response')
->inject('project')
->inject('dbForConsole')
->action(function (string $vcsInstallationId, string $repositoryId, string $rootDirectoryPath, GitHub $github, Response $response, Document $project, Database $dbForConsole) {
$installation = $dbForConsole->getDocument('vcsInstallations', $vcsInstallationId, [
Query::equal('projectInternalId', [$project->getInternalId()])
]);
if ($installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
$installationId = $installation->getAttribute('installationId');
$privateKey = App::getEnv('VCS_GITHUB_PRIVATE_KEY');
$githubAppId = App::getEnv('VCS_GITHUB_APP_ID');
$github->initialiseVariables($installationId, $privateKey, $githubAppId);
$owner = $github->getOwnerName($installationId);
$repositoryName = $github->getRepositoryName($repositoryId);
if (empty($repositoryName)) {
throw new Exception(Exception::REPOSITORY_NOT_FOUND);
}
$files = $github->listRepositoryContents($owner, $repositoryName, $rootDirectoryPath);
$languages = $github->getRepositoryLanguages($owner, $repositoryName);
$detectorFactory = new Detector($files, $languages);
$detectorFactory
->addDetector(new JavaScript())
->addDetector(new PHP())
->addDetector(new Python())
->addDetector(new Dart())
->addDetector(new Swift())
->addDetector(new Ruby())
->addDetector(new Java())
->addDetector(new CPP())
->addDetector(new Deno())
->addDetector(new Dotnet());
$runtime = $detectorFactory->detect();
$detection = [];
$detection['runtime'] = $runtime;
$response->dynamic(new Document($detection), Response::MODEL_DETECTION);
2023-06-15 22:37:28 +12:00
});