Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"require": {
"php": ">=8.0",
"adhocore/jwt": "^1.1",
"utopia-php/cache": "1.0.*"
"utopia-php/cache": "1.0.*",
"utopia-php/fetch": "1.0.*"
},
"require-dev": {
"phpunit/phpunit": "^9.4",
Expand Down
48 changes: 44 additions & 4 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 11 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ services:
- TESTS_GITHUB_APP_IDENTIFIER
- TESTS_GITHUB_INSTALLATION_ID
- TESTS_GITEA_URL=http://gitea:3000
- TESTS_GITEA_REQUEST_CATCHER_URL=http://request-catcher:5000
depends_on:
gitea:
condition: service_healthy
gitea-bootstrap:
condition: service_completed_successfully
request-catcher:
condition: service_started

gitea:
image: gitea/gitea:1.21.5
Expand All @@ -25,6 +28,10 @@ services:
- USER_GID=1000
- GITEA__database__DB_TYPE=sqlite3
- GITEA__security__INSTALL_LOCK=true
- GITEA__webhook__ALLOWED_HOST_LIST=*
- GITEA__webhook__SKIP_TLS_VERIFY=true
- GITEA__webhook__DELIVER_TIMEOUT=10
- GITEA__server__LOCAL_ROOT_URL=http://gitea:3000/
ports:
- "3000:3000"
volumes:
Expand Down Expand Up @@ -54,6 +61,9 @@ services:
TOKEN=$$(su git -c \"gitea admin user generate-access-token --username $$GITEA_ADMIN_USERNAME --token-name $$GITEA_ADMIN_USERNAME-token --scopes all --raw\") &&
echo $$TOKEN > /data/gitea/token.txt
"

request-catcher:
image: appwrite/requestcatcher:1.1.0
ports:
- "5000:5000"
volumes:
gitea-data:
13 changes: 13 additions & 0 deletions src/VCS/Adapter/Git.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,17 @@ abstract public function createBranch(string $owner, string $repositoryName, str
* @return array<mixed> Created PR details
*/
abstract public function createPullRequest(string $owner, string $repositoryName, string $title, string $head, string $base, string $body = ''): array;

/**
* Create a webhook on a repository
*
* @param string $owner Owner of the repository
* @param string $repositoryName Name of the repository
* @param string $url Webhook URL to send events to
* @param string $secret Webhook secret for signature validation
* @param array<string> $events Events to trigger the webhook
* @return int Webhook ID
*/
abstract public function createWebhook(string $owner, string $repositoryName, string $url, string $secret, array $events = ['push', 'pull_request']): int;

}
10 changes: 10 additions & 0 deletions src/VCS/Adapter/Git/GitHub.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@ public function createPullRequest(string $owner, string $repositoryName, string
throw new Exception('Not implemented');
}

/**
* Create a webhook on a repository
*
* Note: Not applicable for GitHub - webhooks are managed via GitHub Apps
*/
public function createWebhook(string $owner, string $repositoryName, string $url, string $secret, array $events = ['push', 'pull_request']): int
{
throw new Exception('Not applicable for GitHub - webhooks are managed via GitHub Apps');
}

/**
* Create a file in a repository
*
Expand Down
170 changes: 168 additions & 2 deletions src/VCS/Adapter/Git/Gitea.php
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,43 @@ public function createPullRequest(string $owner, string $repositoryName, string
return $responseBody;
}

/**
* Create a webhook on a repository
*
* @param string $owner Owner of the repository
* @param string $repositoryName Name of the repository
* @param string $url Webhook URL to send events to
* @param string $secret Webhook secret for signature validation
* @param array<string> $events Events to trigger the webhook
* @return int Webhook ID
*/
public function createWebhook(string $owner, string $repositoryName, string $url, string $secret, array $events = ['push', 'pull_request']): int
{
$response = $this->call(
self::METHOD_POST,
"/repos/{$owner}/{$repositoryName}/hooks",
['Authorization' => "token $this->accessToken"],
[
'type' => 'gitea',
'active' => true,
'events' => $events,
'config' => [
'url' => $url,
'content_type' => 'json',
'secret' => $secret,
],
]
);

$responseHeaders = $response['headers'] ?? [];
$responseHeadersStatusCode = $responseHeaders['status-code'] ?? 0;
if ($responseHeadersStatusCode >= 400) {
throw new Exception("Failed to create webhook: HTTP {$responseHeadersStatusCode}");
}

return (int) ($response['body']['id'] ?? 0);
}

public function createComment(string $owner, string $repositoryName, int $pullRequestNumber, string $comment): string
{
$url = "/repos/{$owner}/{$repositoryName}/issues/{$pullRequestNumber}/comments";
Expand Down Expand Up @@ -739,13 +776,142 @@ public function generateCloneCommand(string $owner, string $repositoryName, stri
throw new Exception("Not implemented yet");
}

/**
* Parses webhook event payload
*
* @param string $event Type of event: push, pull_request, etc
* @param string $payload The webhook payload received from Gitea
* @return array<mixed> Parsed payload as an array
*/
public function getEvent(string $event, string $payload): array
{
throw new Exception("Not implemented yet");
$payload = json_decode($payload, true);

if ($payload === null || !is_array($payload)) {
throw new Exception("Invalid payload.");
}

switch ($event) {
case 'push':
$payloadRepository = $payload['repository'] ?? [];
$payloadRepositoryOwner = $payloadRepository['owner'] ?? [];
$payloadSender = $payload['sender'] ?? [];
$payloadHeadCommit = $payload['head_commit'] ?? [];
$payloadHeadCommitAuthor = $payloadHeadCommit['author'] ?? [];

$branchCreated = $payload['created'] ?? false;
$branchDeleted = $payload['deleted'] ?? false;
$repositoryId = strval($payloadRepository['id'] ?? '');
$repositoryName = $payloadRepository['name'] ?? '';
$branch = str_replace('refs/heads/', '', $payload['ref'] ?? '');
$repositoryUrl = $payloadRepository['html_url'] ?? '';
$branchUrl = !empty($repositoryUrl) && !empty($branch) ? $repositoryUrl . "/src/branch/" . $branch : '';
$commitHash = $payload['after'] ?? '';
$owner = $payloadRepositoryOwner['login'] ?? '';
$authorUrl = $payloadSender['html_url'] ?? '';
$authorAvatarUrl = $payloadSender['avatar_url'] ?? '';
$headCommitAuthorName = $payloadHeadCommitAuthor['name'] ?? '';
$headCommitAuthorEmail = $payloadHeadCommitAuthor['email'] ?? '';
$headCommitMessage = $payloadHeadCommit['message'] ?? '';
$headCommitUrl = $payloadHeadCommit['url'] ?? '';

$affectedFiles = [];
foreach (($payload['commits'] ?? []) as $commit) {
foreach (($commit['added'] ?? []) as $added) {
$affectedFiles[$added] = true;
}

foreach (($commit['removed'] ?? []) as $removed) {
$affectedFiles[$removed] = true;
}

foreach (($commit['modified'] ?? []) as $modified) {
$affectedFiles[$modified] = true;
}
}

return [
'branchCreated' => $branchCreated,
'branchDeleted' => $branchDeleted,
'branch' => $branch,
'branchUrl' => $branchUrl,
'repositoryId' => $repositoryId,
'repositoryName' => $repositoryName,
'repositoryUrl' => $repositoryUrl,
'installationId' => '', // Gitea doesn't have installations
'commitHash' => $commitHash,
'owner' => $owner,
'authorUrl' => $authorUrl,
'authorAvatarUrl' => $authorAvatarUrl,
'headCommitAuthorName' => $headCommitAuthorName,
'headCommitAuthorEmail' => $headCommitAuthorEmail,
'headCommitMessage' => $headCommitMessage,
'headCommitUrl' => $headCommitUrl,
'external' => false,
'pullRequestNumber' => '',
'action' => '',
'affectedFiles' => \array_keys($affectedFiles),
];

case 'pull_request':
$payloadRepository = $payload['repository'] ?? [];
$payloadRepositoryOwner = $payloadRepository['owner'] ?? [];
$payloadSender = $payload['sender'] ?? [];
$payloadPullRequest = $payload['pull_request'] ?? [];
$payloadPullRequestHead = $payloadPullRequest['head'] ?? [];
$payloadPullRequestHeadRepo = $payloadPullRequestHead['repo'] ?? [];
$payloadPullRequestUser = $payloadPullRequest['user'] ?? [];
$payloadPullRequestBase = $payloadPullRequest['base'] ?? [];

$repositoryId = strval($payloadRepository['id'] ?? '');
$branch = $payloadPullRequestHead['ref'] ?? '';
$repositoryName = $payloadRepository['name'] ?? '';
$repositoryUrl = $payloadRepository['html_url'] ?? '';
$branchUrl = !empty($repositoryUrl) && !empty($branch) ? $repositoryUrl . "/src/branch/" . $branch : '';
$pullRequestNumber = $payload['number'] ?? '';
$action = $payload['action'] ?? '';
$owner = $payloadRepositoryOwner['login'] ?? '';
$authorUrl = $payloadSender['html_url'] ?? '';
$authorAvatarUrl = $payloadPullRequestUser['avatar_url'] ?? '';
$commitHash = $payloadPullRequestHead['sha'] ?? '';
$headCommitUrl = $repositoryUrl ? $repositoryUrl . "/commit/" . $commitHash : '';

// Check if PR is from a fork (external)
$headRepoFullName = $payloadPullRequestHeadRepo['full_name'] ?? '';
$baseRepoFullName = $payloadRepository['full_name'] ?? '';
$external = !empty($headRepoFullName) && !empty($baseRepoFullName) && $headRepoFullName !== $baseRepoFullName;

return [
'branch' => $branch,
'branchUrl' => $branchUrl,
'repositoryId' => $repositoryId,
'repositoryName' => $repositoryName,
'repositoryUrl' => $repositoryUrl,
'installationId' => '', // Gitea doesn't have installations
'commitHash' => $commitHash,
'owner' => $owner,
'authorUrl' => $authorUrl,
'authorAvatarUrl' => $authorAvatarUrl,
'headCommitUrl' => $headCommitUrl,
'external' => $external,
'pullRequestNumber' => $pullRequestNumber,
'action' => $action,
];
}

return [];
}

/**
* Validate webhook event
*
* @param string $payload Raw body of HTTP request
* @param string $signature Signature provided by Gitea in X-Gitea-Signature header
* @param string $signatureKey Webhook secret configured on Gitea
* @return bool
*/
public function validateWebhookEvent(string $payload, string $signature, string $signatureKey): bool
{
throw new Exception("Not implemented yet");
return hash_equals($signature, hash_hmac('sha256', $payload, $signatureKey));
}
}
Loading
Loading