coolify/app/Jobs/ProcessGithubPullRequestWebhook.php
Andras Bacsai 1da1f32f0e refactor: use forceCreate() for internal model creation
Replace create() with forceCreate() across internal model creation operations to bypass mass assignment protection. This is appropriate for internal code that constructs complete model state without user input.

Add InternalModelCreationMassAssignmentTest to ensure internal model creation behavior is properly tested. Optimize imports by using shortened Livewire attribute references and removing unused imports.
2026-03-30 13:04:11 +02:00

152 lines
5.4 KiB
PHP

<?php
namespace App\Jobs;
use App\Actions\Application\CleanupPreviewDeployment;
use App\Enums\ProcessStatus;
use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Models\GithubApp;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Visus\Cuid2\Cuid2;
class ProcessGithubPullRequestWebhook implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 3;
public int $timeout = 60;
public array $backoff = [30, 60, 120];
public function __construct(
public int $applicationId,
public ?int $githubAppId,
public string $action,
public int $pullRequestId,
public string $pullRequestHtmlUrl,
public ?string $beforeSha,
public ?string $afterSha,
public string $commitSha,
public ?string $authorAssociation,
public string $fullName,
) {
$this->onQueue('high');
}
public function handle(): void
{
$application = Application::find($this->applicationId);
if (! $application) {
return;
}
$githubApp = $this->githubAppId ? GithubApp::find($this->githubAppId) : null;
if ($this->action === 'closed' || $this->action === 'close') {
$this->handleClosedAction($application);
return;
}
if ($this->action === 'opened' || $this->action === 'synchronize' || $this->action === 'reopened') {
$this->handleOpenAction($application, $githubApp);
}
}
private function handleClosedAction(Application $application): void
{
$found = ApplicationPreview::where('application_id', $application->id)
->where('pull_request_id', $this->pullRequestId)
->first();
if ($found) {
ApplicationPullRequestUpdateJob::dispatchSync(
application: $application,
preview: $found,
status: ProcessStatus::CLOSED
);
CleanupPreviewDeployment::run($application, $this->pullRequestId, $found);
}
}
private function handleOpenAction(Application $application, ?GithubApp $githubApp): void
{
if (! $application->isPRDeployable()) {
return;
}
// Check if PR deployments from public contributors are restricted
if (! $application->settings->is_pr_deployments_public_enabled) {
$trustedAssociations = ['OWNER', 'MEMBER', 'COLLABORATOR', 'CONTRIBUTOR'];
if (! in_array($this->authorAssociation, $trustedAssociations)) {
return;
}
}
// Get changed files for watch path filtering
$changed_files = collect();
$repository_parts = explode('/', $this->fullName);
$owner = $repository_parts[0] ?? '';
$repo = $repository_parts[1] ?? '';
if ($this->action === 'synchronize' && $this->beforeSha && $this->afterSha) {
// For synchronize events, get files changed between before and after commits
$changed_files = collect(getGithubCommitRangeFiles($githubApp, $owner, $repo, $this->beforeSha, $this->afterSha));
} elseif ($this->action === 'opened' || $this->action === 'reopened') {
// For opened/reopened events, get all files in the PR
$changed_files = collect(getGithubPullRequestFiles($githubApp, $owner, $repo, $this->pullRequestId));
}
// Apply watch path filtering
$is_watch_path_triggered = $application->isWatchPathsTriggered($changed_files);
if (! $is_watch_path_triggered && ! blank($application->watch_paths)) {
return;
}
// Create ApplicationPreview if not exists
$found = ApplicationPreview::where('application_id', $application->id)
->where('pull_request_id', $this->pullRequestId)
->first();
if (! $found) {
if ($application->build_pack === 'dockercompose') {
$preview = ApplicationPreview::forceCreate([
'git_type' => 'github',
'application_id' => $application->id,
'pull_request_id' => $this->pullRequestId,
'pull_request_html_url' => $this->pullRequestHtmlUrl,
'docker_compose_domains' => $application->docker_compose_domains,
]);
$preview->generate_preview_fqdn_compose();
} else {
$preview = ApplicationPreview::forceCreate([
'git_type' => 'github',
'application_id' => $application->id,
'pull_request_id' => $this->pullRequestId,
'pull_request_html_url' => $this->pullRequestHtmlUrl,
]);
$preview->generate_preview_fqdn();
}
}
// Queue the deployment
$deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
pull_request_id: $this->pullRequestId,
deployment_uuid: $deployment_uuid,
force_rebuild: false,
commit: $this->commitSha,
is_webhook: true,
git_type: 'github'
);
}
}