Replace all uses of `forceFill`, `forceCreate`, and `forceFill` with their non-force equivalents across models, actions, controllers, and Livewire components. Add explicit `$fillable` arrays to all affected Eloquent models to enforce mass assignment protection. Add ModelFillableCreationTest and ModelFillableRegressionTest to verify that model creation respects fillable constraints and prevent regressions.
152 lines
5.4 KiB
PHP
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::create([
|
|
'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::create([
|
|
'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'
|
|
);
|
|
}
|
|
}
|