Merge remote-tracking branch 'origin/next' into pr-7764-shadow/add-shared-server-env

This commit is contained in:
Andras Bacsai 2026-03-31 13:51:06 +02:00
commit 9c646b0a9e
81 changed files with 1600 additions and 156 deletions

View file

@ -21,7 +21,7 @@ public function reset(User $user, array $input): void
'password' => ['required', Password::defaults(), 'confirmed'], 'password' => ['required', Password::defaults(), 'confirmed'],
])->validate(); ])->validate();
$user->forceFill([ $user->fill([
'password' => Hash::make($input['password']), 'password' => Hash::make($input['password']),
])->save(); ])->save();
$user->deleteAllSessions(); $user->deleteAllSessions();

View file

@ -24,7 +24,7 @@ public function update(User $user, array $input): void
'current_password.current_password' => __('The provided password does not match your current password.'), 'current_password.current_password' => __('The provided password does not match your current password.'),
])->validateWithBag('updatePassword'); ])->validateWithBag('updatePassword');
$user->forceFill([ $user->fill([
'password' => Hash::make($input['password']), 'password' => Hash::make($input['password']),
])->save(); ])->save();
} }

View file

@ -35,7 +35,7 @@ public function update(User $user, array $input): void
) { ) {
$this->updateVerifiedUser($user, $input); $this->updateVerifiedUser($user, $input);
} else { } else {
$user->forceFill([ $user->fill([
'name' => $input['name'], 'name' => $input['name'],
'email' => $input['email'], 'email' => $input['email'],
])->save(); ])->save();
@ -49,7 +49,7 @@ public function update(User $user, array $input): void
*/ */
protected function updateVerifiedUser(User $user, array $input): void protected function updateVerifiedUser(User $user, array $input): void
{ {
$user->forceFill([ $user->fill([
'name' => $input['name'], 'name' => $input['name'],
'email' => $input['email'], 'email' => $input['email'],
'email_verified_at' => null, 'email_verified_at' => null,

View file

@ -49,7 +49,7 @@ public function handle(Server $server)
}'); }');
$found = StandaloneDocker::where('server_id', $server->id); $found = StandaloneDocker::where('server_id', $server->id);
if ($found->count() == 0 && $server->id) { if ($found->count() == 0 && $server->id) {
StandaloneDocker::forceCreate([ StandaloneDocker::create([
'name' => 'coolify', 'name' => 'coolify',
'network' => 'coolify', 'network' => 'coolify',
'server_id' => $server->id, 'server_id' => $server->id,

View file

@ -136,7 +136,7 @@ public function handle()
$application = Application::all()->first(); $application = Application::all()->first();
$preview = ApplicationPreview::all()->first(); $preview = ApplicationPreview::all()->first();
if (! $preview) { if (! $preview) {
$preview = ApplicationPreview::forceCreate([ $preview = ApplicationPreview::create([
'application_id' => $application->id, 'application_id' => $application->id,
'pull_request_id' => 1, 'pull_request_id' => 1,
'pull_request_html_url' => 'http://example.com', 'pull_request_html_url' => 'http://example.com',

View file

@ -258,7 +258,7 @@ public function create_project(Request $request)
], 422); ], 422);
} }
$project = Project::forceCreate([ $project = Project::create([
'name' => $request->name, 'name' => $request->name,
'description' => $request->description, 'description' => $request->description,
'team_id' => $teamId, 'team_id' => $teamId,

View file

@ -432,7 +432,7 @@ public function create_service(Request $request)
if (in_array($oneClickServiceName, NEEDS_TO_CONNECT_TO_PREDEFINED_NETWORK)) { if (in_array($oneClickServiceName, NEEDS_TO_CONNECT_TO_PREDEFINED_NETWORK)) {
data_set($servicePayload, 'connect_to_docker_network', true); data_set($servicePayload, 'connect_to_docker_network', true);
} }
$service = Service::forceCreate($servicePayload); $service = Service::create($servicePayload);
$service->name = $request->name ?? "$oneClickServiceName-".$service->uuid; $service->name = $request->name ?? "$oneClickServiceName-".$service->uuid;
$service->description = $request->description; $service->description = $request->description;
if ($request->has('is_container_label_escape_enabled')) { if ($request->has('is_container_label_escape_enabled')) {

View file

@ -119,7 +119,7 @@ public function manual(Request $request)
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) { if (! $found) {
if ($application->build_pack === 'dockercompose') { if ($application->build_pack === 'dockercompose') {
$pr_app = ApplicationPreview::forceCreate([ $pr_app = ApplicationPreview::create([
'git_type' => 'bitbucket', 'git_type' => 'bitbucket',
'application_id' => $application->id, 'application_id' => $application->id,
'pull_request_id' => $pull_request_id, 'pull_request_id' => $pull_request_id,
@ -128,7 +128,7 @@ public function manual(Request $request)
]); ]);
$pr_app->generate_preview_fqdn_compose(); $pr_app->generate_preview_fqdn_compose();
} else { } else {
$pr_app = ApplicationPreview::forceCreate([ $pr_app = ApplicationPreview::create([
'git_type' => 'bitbucket', 'git_type' => 'bitbucket',
'application_id' => $application->id, 'application_id' => $application->id,
'pull_request_id' => $pull_request_id, 'pull_request_id' => $pull_request_id,

View file

@ -144,7 +144,7 @@ public function manual(Request $request)
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) { if (! $found) {
if ($application->build_pack === 'dockercompose') { if ($application->build_pack === 'dockercompose') {
$pr_app = ApplicationPreview::forceCreate([ $pr_app = ApplicationPreview::create([
'git_type' => 'gitea', 'git_type' => 'gitea',
'application_id' => $application->id, 'application_id' => $application->id,
'pull_request_id' => $pull_request_id, 'pull_request_id' => $pull_request_id,
@ -153,7 +153,7 @@ public function manual(Request $request)
]); ]);
$pr_app->generate_preview_fqdn_compose(); $pr_app->generate_preview_fqdn_compose();
} else { } else {
$pr_app = ApplicationPreview::forceCreate([ $pr_app = ApplicationPreview::create([
'git_type' => 'gitea', 'git_type' => 'gitea',
'application_id' => $application->id, 'application_id' => $application->id,
'pull_request_id' => $pull_request_id, 'pull_request_id' => $pull_request_id,

View file

@ -177,7 +177,7 @@ public function manual(Request $request)
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first(); $found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) { if (! $found) {
if ($application->build_pack === 'dockercompose') { if ($application->build_pack === 'dockercompose') {
$pr_app = ApplicationPreview::forceCreate([ $pr_app = ApplicationPreview::create([
'git_type' => 'gitlab', 'git_type' => 'gitlab',
'application_id' => $application->id, 'application_id' => $application->id,
'pull_request_id' => $pull_request_id, 'pull_request_id' => $pull_request_id,
@ -186,7 +186,7 @@ public function manual(Request $request)
]); ]);
$pr_app->generate_preview_fqdn_compose(); $pr_app->generate_preview_fqdn_compose();
} else { } else {
$pr_app = ApplicationPreview::forceCreate([ $pr_app = ApplicationPreview::create([
'git_type' => 'gitlab', 'git_type' => 'gitlab',
'application_id' => $application->id, 'application_id' => $application->id,
'pull_request_id' => $pull_request_id, 'pull_request_id' => $pull_request_id,

View file

@ -118,7 +118,7 @@ private function handleOpenAction(Application $application, ?GithubApp $githubAp
if (! $found) { if (! $found) {
if ($application->build_pack === 'dockercompose') { if ($application->build_pack === 'dockercompose') {
$preview = ApplicationPreview::forceCreate([ $preview = ApplicationPreview::create([
'git_type' => 'github', 'git_type' => 'github',
'application_id' => $application->id, 'application_id' => $application->id,
'pull_request_id' => $this->pullRequestId, 'pull_request_id' => $this->pullRequestId,
@ -127,7 +127,7 @@ private function handleOpenAction(Application $application, ?GithubApp $githubAp
]); ]);
$preview->generate_preview_fqdn_compose(); $preview->generate_preview_fqdn_compose();
} else { } else {
$preview = ApplicationPreview::forceCreate([ $preview = ApplicationPreview::create([
'git_type' => 'github', 'git_type' => 'github',
'application_id' => $application->id, 'application_id' => $application->id,
'pull_request_id' => $this->pullRequestId, 'pull_request_id' => $this->pullRequestId,

View file

@ -441,7 +441,7 @@ public function selectExistingProject()
public function createNewProject() public function createNewProject()
{ {
$this->createdProject = Project::forceCreate([ $this->createdProject = Project::create([
'name' => 'My first project', 'name' => 'My first project',
'team_id' => currentTeam()->id, 'team_id' => currentTeam()->id,
'uuid' => (string) new Cuid2, 'uuid' => (string) new Cuid2,

View file

@ -77,7 +77,7 @@ public function submit()
if ($found) { if ($found) {
throw new \Exception('Network already added to this server.'); throw new \Exception('Network already added to this server.');
} else { } else {
$docker = SwarmDocker::forceCreate([ $docker = SwarmDocker::create([
'name' => $this->name, 'name' => $this->name,
'network' => $this->network, 'network' => $this->network,
'server_id' => $this->selectedServer->id, 'server_id' => $this->selectedServer->id,
@ -88,7 +88,7 @@ public function submit()
if ($found) { if ($found) {
throw new \Exception('Network already added to this server.'); throw new \Exception('Network already added to this server.');
} else { } else {
$docker = StandaloneDocker::forceCreate([ $docker = StandaloneDocker::create([
'name' => $this->name, 'name' => $this->name,
'network' => $this->network, 'network' => $this->network,
'server_id' => $this->selectedServer->id, 'server_id' => $this->selectedServer->id,

View file

@ -48,7 +48,7 @@ public function submit()
$this->rateLimit(10); $this->rateLimit(10);
$this->validate(); $this->validate();
$firstLogin = auth()->user()->created_at == auth()->user()->updated_at; $firstLogin = auth()->user()->created_at == auth()->user()->updated_at;
auth()->user()->forceFill([ auth()->user()->fill([
'password' => Hash::make($this->password), 'password' => Hash::make($this->password),
'force_password_reset' => false, 'force_password_reset' => false,
])->save(); ])->save();

View file

@ -30,7 +30,7 @@ public function submit()
{ {
try { try {
$this->validate(); $this->validate();
$project = Project::forceCreate([ $project = Project::create([
'name' => $this->name, 'name' => $this->name,
'description' => $this->description, 'description' => $this->description,
'team_id' => currentTeam()->id, 'team_id' => currentTeam()->id,

View file

@ -196,7 +196,7 @@ public function add(int $pull_request_id, ?string $pull_request_html_url = null,
$this->setDeploymentUuid(); $this->setDeploymentUuid();
$found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first(); $found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found && ! is_null($pull_request_html_url)) { if (! $found && ! is_null($pull_request_html_url)) {
$found = ApplicationPreview::forceCreate([ $found = ApplicationPreview::create([
'application_id' => $this->application->id, 'application_id' => $this->application->id,
'pull_request_id' => $pull_request_id, 'pull_request_id' => $pull_request_id,
'pull_request_html_url' => $pull_request_html_url, 'pull_request_html_url' => $pull_request_html_url,
@ -210,7 +210,7 @@ public function add(int $pull_request_id, ?string $pull_request_html_url = null,
$this->setDeploymentUuid(); $this->setDeploymentUuid();
$found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first(); $found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found && (! is_null($pull_request_html_url) || ($this->application->build_pack === 'dockerimage' && str($docker_registry_image_tag)->isNotEmpty()))) { if (! $found && (! is_null($pull_request_html_url) || ($this->application->build_pack === 'dockerimage' && str($docker_registry_image_tag)->isNotEmpty()))) {
$found = ApplicationPreview::forceCreate([ $found = ApplicationPreview::create([
'application_id' => $this->application->id, 'application_id' => $this->application->id,
'pull_request_id' => $pull_request_id, 'pull_request_id' => $pull_request_id,
'pull_request_html_url' => $pull_request_html_url ?? '', 'pull_request_html_url' => $pull_request_html_url ?? '',
@ -262,7 +262,7 @@ public function deploy(int $pull_request_id, ?string $pull_request_html_url = nu
$this->setDeploymentUuid(); $this->setDeploymentUuid();
$found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first(); $found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found && (! is_null($pull_request_html_url) || ($this->application->build_pack === 'dockerimage' && str($docker_registry_image_tag)->isNotEmpty()))) { if (! $found && (! is_null($pull_request_html_url) || ($this->application->build_pack === 'dockerimage' && str($docker_registry_image_tag)->isNotEmpty()))) {
$found = ApplicationPreview::forceCreate([ $found = ApplicationPreview::create([
'application_id' => $this->application->id, 'application_id' => $this->application->id,
'pull_request_id' => $pull_request_id, 'pull_request_id' => $pull_request_id,
'pull_request_html_url' => $pull_request_html_url ?? '', 'pull_request_html_url' => $pull_request_html_url ?? '',

View file

@ -100,7 +100,7 @@ public function clone(string $type)
if ($foundProject) { if ($foundProject) {
throw new \Exception('Project with the same name already exists.'); throw new \Exception('Project with the same name already exists.');
} }
$project = Project::forceCreate([ $project = Project::create([
'name' => $this->newName, 'name' => $this->newName,
'team_id' => currentTeam()->id, 'team_id' => currentTeam()->id,
'description' => $this->project->description.' (clone)', 'description' => $this->project->description.' (clone)',
@ -139,7 +139,7 @@ public function clone(string $type)
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->forceFill([ ])->fill([
'uuid' => $uuid, 'uuid' => $uuid,
'status' => 'exited', 'status' => 'exited',
'started_at' => null, 'started_at' => null,
@ -188,7 +188,7 @@ public function clone(string $type)
'created_at', 'created_at',
'updated_at', 'updated_at',
'uuid', 'uuid',
])->forceFill([ ])->fill([
'name' => $newName, 'name' => $newName,
'resource_id' => $newDatabase->id, 'resource_id' => $newDatabase->id,
]); ]);
@ -217,7 +217,7 @@ public function clone(string $type)
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->forceFill([ ])->fill([
'resource_id' => $newDatabase->id, 'resource_id' => $newDatabase->id,
]); ]);
$newStorage->save(); $newStorage->save();
@ -230,7 +230,7 @@ public function clone(string $type)
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->forceFill([ ])->fill([
'uuid' => $uuid, 'uuid' => $uuid,
'database_id' => $newDatabase->id, 'database_id' => $newDatabase->id,
'database_type' => $newDatabase->getMorphClass(), 'database_type' => $newDatabase->getMorphClass(),
@ -248,7 +248,7 @@ public function clone(string $type)
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->forceFill($payload); ])->fill($payload);
$newEnvironmentVariable->save(); $newEnvironmentVariable->save();
} }
} }
@ -259,7 +259,7 @@ public function clone(string $type)
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->forceFill([ ])->fill([
'uuid' => $uuid, 'uuid' => $uuid,
'environment_id' => $environment->id, 'environment_id' => $environment->id,
'destination_id' => $this->selectedDestination, 'destination_id' => $this->selectedDestination,
@ -277,7 +277,7 @@ public function clone(string $type)
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->forceFill([ ])->fill([
'uuid' => (string) new Cuid2, 'uuid' => (string) new Cuid2,
'service_id' => $newService->id, 'service_id' => $newService->id,
'team_id' => currentTeam()->id, 'team_id' => currentTeam()->id,
@ -291,7 +291,7 @@ public function clone(string $type)
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->forceFill([ ])->fill([
'resourceable_id' => $newService->id, 'resourceable_id' => $newService->id,
'resourceable_type' => $newService->getMorphClass(), 'resourceable_type' => $newService->getMorphClass(),
]); ]);
@ -299,7 +299,7 @@ public function clone(string $type)
} }
foreach ($newService->applications() as $application) { foreach ($newService->applications() as $application) {
$application->forceFill([ $application->fill([
'status' => 'exited', 'status' => 'exited',
])->save(); ])->save();
@ -317,7 +317,7 @@ public function clone(string $type)
'created_at', 'created_at',
'updated_at', 'updated_at',
'uuid', 'uuid',
])->forceFill([ ])->fill([
'name' => $newName, 'name' => $newName,
'resource_id' => $application->id, 'resource_id' => $application->id,
]); ]);
@ -346,7 +346,7 @@ public function clone(string $type)
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->forceFill([ ])->fill([
'resource_id' => $application->id, 'resource_id' => $application->id,
]); ]);
$newStorage->save(); $newStorage->save();
@ -354,7 +354,7 @@ public function clone(string $type)
} }
foreach ($newService->databases() as $database) { foreach ($newService->databases() as $database) {
$database->forceFill([ $database->fill([
'status' => 'exited', 'status' => 'exited',
])->save(); ])->save();
@ -372,7 +372,7 @@ public function clone(string $type)
'created_at', 'created_at',
'updated_at', 'updated_at',
'uuid', 'uuid',
])->forceFill([ ])->fill([
'name' => $newName, 'name' => $newName,
'resource_id' => $database->id, 'resource_id' => $database->id,
]); ]);
@ -401,7 +401,7 @@ public function clone(string $type)
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->forceFill([ ])->fill([
'resource_id' => $database->id, 'resource_id' => $database->id,
]); ]);
$newStorage->save(); $newStorage->save();
@ -414,7 +414,7 @@ public function clone(string $type)
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->forceFill([ ])->fill([
'uuid' => $uuid, 'uuid' => $uuid,
'database_id' => $database->id, 'database_id' => $database->id,
'database_type' => $database->getMorphClass(), 'database_type' => $database->getMorphClass(),

View file

@ -54,7 +54,7 @@ public function submit()
} }
$destination_class = $destination->getMorphClass(); $destination_class = $destination->getMorphClass();
$service = Service::forceCreate([ $service = Service::create([
'docker_compose_raw' => $this->dockerComposeRaw, 'docker_compose_raw' => $this->dockerComposeRaw,
'environment_id' => $environment->id, 'environment_id' => $environment->id,
'server_id' => (int) $server_id, 'server_id' => (int) $server_id,

View file

@ -133,7 +133,7 @@ public function submit()
// Determine the image tag based on whether it's a hash or regular tag // Determine the image tag based on whether it's a hash or regular tag
$imageTag = $parser->isImageHash() ? 'sha256-'.$parser->getTag() : $parser->getTag(); $imageTag = $parser->isImageHash() ? 'sha256-'.$parser->getTag() : $parser->getTag();
$application = Application::forceCreate([ $application = Application::create([
'name' => 'docker-image-'.new Cuid2, 'name' => 'docker-image-'.new Cuid2,
'repository_project_id' => 0, 'repository_project_id' => 0,
'git_repository' => 'coollabsio/coolify', 'git_repository' => 'coollabsio/coolify',

View file

@ -10,7 +10,7 @@ class EmptyProject extends Component
{ {
public function createEmptyProject() public function createEmptyProject()
{ {
$project = Project::forceCreate([ $project = Project::create([
'name' => generate_random_name(), 'name' => generate_random_name(),
'team_id' => currentTeam()->id, 'team_id' => currentTeam()->id,
'uuid' => (string) new Cuid2, 'uuid' => (string) new Cuid2,

View file

@ -99,6 +99,8 @@ public function updatedBuildPack()
public function loadRepositories($github_app_id) public function loadRepositories($github_app_id)
{ {
$this->repositories = collect(); $this->repositories = collect();
$this->branches = collect();
$this->total_branches_count = 0;
$this->page = 1; $this->page = 1;
$this->selected_github_app_id = $github_app_id; $this->selected_github_app_id = $github_app_id;
$this->github_app = GithubApp::where('id', $github_app_id)->first(); $this->github_app = GithubApp::where('id', $github_app_id)->first();
@ -189,7 +191,7 @@ public function submit()
$project = Project::ownedByCurrentTeam()->where('uuid', $this->parameters['project_uuid'])->firstOrFail(); $project = Project::ownedByCurrentTeam()->where('uuid', $this->parameters['project_uuid'])->firstOrFail();
$environment = $project->environments()->where('uuid', $this->parameters['environment_uuid'])->firstOrFail(); $environment = $project->environments()->where('uuid', $this->parameters['environment_uuid'])->firstOrFail();
$application = Application::forceCreate([ $application = Application::create([
'name' => generate_application_name($this->selected_repository_owner.'/'.$this->selected_repository_repo, $this->selected_branch_name), 'name' => generate_application_name($this->selected_repository_owner.'/'.$this->selected_repository_repo, $this->selected_branch_name),
'repository_project_id' => $this->selected_repository_id, 'repository_project_id' => $this->selected_repository_id,
'git_repository' => str($this->selected_repository_owner)->trim()->toString().'/'.str($this->selected_repository_repo)->trim()->toString(), 'git_repository' => str($this->selected_repository_owner)->trim()->toString().'/'.str($this->selected_repository_repo)->trim()->toString(),

View file

@ -183,7 +183,7 @@ public function submit()
$application_init['docker_compose_location'] = $this->docker_compose_location; $application_init['docker_compose_location'] = $this->docker_compose_location;
$application_init['base_directory'] = $this->base_directory; $application_init['base_directory'] = $this->base_directory;
} }
$application = Application::forceCreate($application_init); $application = Application::create($application_init);
$application->settings->is_static = $this->is_static; $application->settings->is_static = $this->is_static;
$application->settings->save(); $application->settings->save();

View file

@ -299,7 +299,7 @@ public function submit()
$new_service['source_id'] = $this->git_source->id; $new_service['source_id'] = $this->git_source->id;
$new_service['source_type'] = $this->git_source->getMorphClass(); $new_service['source_type'] = $this->git_source->getMorphClass();
} }
$service = Service::forceCreate($new_service); $service = Service::create($new_service);
return redirect()->route('project.service.configuration', [ return redirect()->route('project.service.configuration', [
'service_uuid' => $service->uuid, 'service_uuid' => $service->uuid,
@ -346,7 +346,7 @@ public function submit()
$application_init['docker_compose_location'] = $this->docker_compose_location; $application_init['docker_compose_location'] = $this->docker_compose_location;
$application_init['base_directory'] = $this->base_directory; $application_init['base_directory'] = $this->base_directory;
} }
$application = Application::forceCreate($application_init); $application = Application::create($application_init);
$application->settings->is_static = $this->isStatic; $application->settings->is_static = $this->isStatic;
$application->settings->save(); $application->settings->save();

View file

@ -52,7 +52,7 @@ public function submit()
if (! $port) { if (! $port) {
$port = 80; $port = 80;
} }
$application = Application::forceCreate([ $application = Application::create([
'name' => 'dockerfile-'.new Cuid2, 'name' => 'dockerfile-'.new Cuid2,
'repository_project_id' => 0, 'repository_project_id' => 0,
'git_repository' => 'coollabsio/coolify', 'git_repository' => 'coollabsio/coolify',

View file

@ -91,7 +91,7 @@ public function mount()
if (in_array($oneClickServiceName, NEEDS_TO_CONNECT_TO_PREDEFINED_NETWORK)) { if (in_array($oneClickServiceName, NEEDS_TO_CONNECT_TO_PREDEFINED_NETWORK)) {
data_set($service_payload, 'connect_to_docker_network', true); data_set($service_payload, 'connect_to_docker_network', true);
} }
$service = Service::forceCreate($service_payload); $service = Service::create($service_payload);
$service->name = "$oneClickServiceName-".$service->uuid; $service->name = "$oneClickServiceName-".$service->uuid;
$service->save(); $service->save();
if ($oneClickDotEnvs?->count() > 0) { if ($oneClickDotEnvs?->count() > 0) {

View file

@ -94,7 +94,7 @@ public function cloneTo($destination_id)
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->forceFill([ ])->fill([
'uuid' => $uuid, 'uuid' => $uuid,
'name' => $this->resource->name.'-clone-'.$uuid, 'name' => $this->resource->name.'-clone-'.$uuid,
'status' => 'exited', 'status' => 'exited',
@ -143,7 +143,7 @@ public function cloneTo($destination_id)
'created_at', 'created_at',
'updated_at', 'updated_at',
'uuid', 'uuid',
])->forceFill([ ])->fill([
'name' => $newName, 'name' => $newName,
'resource_id' => $new_resource->id, 'resource_id' => $new_resource->id,
]); ]);
@ -172,7 +172,7 @@ public function cloneTo($destination_id)
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->forceFill([ ])->fill([
'resource_id' => $new_resource->id, 'resource_id' => $new_resource->id,
]); ]);
$newStorage->save(); $newStorage->save();
@ -185,7 +185,7 @@ public function cloneTo($destination_id)
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->forceFill([ ])->fill([
'uuid' => $uuid, 'uuid' => $uuid,
'database_id' => $new_resource->id, 'database_id' => $new_resource->id,
'database_type' => $new_resource->getMorphClass(), 'database_type' => $new_resource->getMorphClass(),
@ -204,7 +204,7 @@ public function cloneTo($destination_id)
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->forceFill($payload); ])->fill($payload);
$newEnvironmentVariable->save(); $newEnvironmentVariable->save();
} }
@ -221,7 +221,7 @@ public function cloneTo($destination_id)
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->forceFill([ ])->fill([
'uuid' => $uuid, 'uuid' => $uuid,
'name' => $this->resource->name.'-clone-'.$uuid, 'name' => $this->resource->name.'-clone-'.$uuid,
'destination_id' => $new_destination->id, 'destination_id' => $new_destination->id,
@ -242,7 +242,7 @@ public function cloneTo($destination_id)
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->forceFill([ ])->fill([
'uuid' => (string) new Cuid2, 'uuid' => (string) new Cuid2,
'service_id' => $new_resource->id, 'service_id' => $new_resource->id,
'team_id' => currentTeam()->id, 'team_id' => currentTeam()->id,
@ -256,7 +256,7 @@ public function cloneTo($destination_id)
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->forceFill([ ])->fill([
'resourceable_id' => $new_resource->id, 'resourceable_id' => $new_resource->id,
'resourceable_type' => $new_resource->getMorphClass(), 'resourceable_type' => $new_resource->getMorphClass(),
]); ]);
@ -264,7 +264,7 @@ public function cloneTo($destination_id)
} }
foreach ($new_resource->applications() as $application) { foreach ($new_resource->applications() as $application) {
$application->forceFill([ $application->fill([
'status' => 'exited', 'status' => 'exited',
])->save(); ])->save();
@ -282,7 +282,7 @@ public function cloneTo($destination_id)
'created_at', 'created_at',
'updated_at', 'updated_at',
'uuid', 'uuid',
])->forceFill([ ])->fill([
'name' => $newName, 'name' => $newName,
'resource_id' => $application->id, 'resource_id' => $application->id,
]); ]);
@ -307,7 +307,7 @@ public function cloneTo($destination_id)
} }
foreach ($new_resource->databases() as $database) { foreach ($new_resource->databases() as $database) {
$database->forceFill([ $database->fill([
'status' => 'exited', 'status' => 'exited',
])->save(); ])->save();
@ -325,7 +325,7 @@ public function cloneTo($destination_id)
'created_at', 'created_at',
'updated_at', 'updated_at',
'uuid', 'uuid',
])->forceFill([ ])->fill([
'name' => $newName, 'name' => $newName,
'resource_id' => $database->id, 'resource_id' => $database->id,
]); ]);
@ -366,7 +366,7 @@ public function moveTo($environment_id)
try { try {
$this->authorize('update', $this->resource); $this->authorize('update', $this->resource);
$new_environment = Environment::ownedByCurrentTeam()->findOrFail($environment_id); $new_environment = Environment::ownedByCurrentTeam()->findOrFail($environment_id);
$this->resource->forceFill([ $this->resource->fill([
'environment_id' => $environment_id, 'environment_id' => $environment_id,
])->save(); ])->save();
if ($this->resource->type() === 'application') { if ($this->resource->type() === 'application') {

View file

@ -42,7 +42,7 @@ public function submit()
{ {
try { try {
$this->validate(); $this->validate();
$environment = Environment::forceCreate([ $environment = Environment::create([
'name' => $this->name, 'name' => $this->name,
'project_id' => $this->project->id, 'project_id' => $this->project->id,
'uuid' => (string) new Cuid2, 'uuid' => (string) new Cuid2,

View file

@ -43,7 +43,7 @@ public function add($name)
return; return;
} else { } else {
SwarmDocker::forceCreate([ SwarmDocker::create([
'name' => $this->server->name.'-'.$name, 'name' => $this->server->name.'-'.$name,
'network' => $this->name, 'network' => $this->name,
'server_id' => $this->server->id, 'server_id' => $this->server->id,
@ -57,7 +57,7 @@ public function add($name)
return; return;
} else { } else {
StandaloneDocker::forceCreate([ StandaloneDocker::create([
'name' => $this->server->name.'-'.$name, 'name' => $this->server->name.'-'.$name,
'network' => $name, 'network' => $name,
'server_id' => $this->server->id, 'server_id' => $this->server->id,

View file

@ -83,7 +83,8 @@ public function addCoolifyDatabase()
$postgres_password = $envs['POSTGRES_PASSWORD']; $postgres_password = $envs['POSTGRES_PASSWORD'];
$postgres_user = $envs['POSTGRES_USER']; $postgres_user = $envs['POSTGRES_USER'];
$postgres_db = $envs['POSTGRES_DB']; $postgres_db = $envs['POSTGRES_DB'];
$this->database = StandalonePostgresql::forceCreate([ $this->database = new StandalonePostgresql;
$this->database->forceFill([
'id' => 0, 'id' => 0,
'name' => 'coolify-db', 'name' => 'coolify-db',
'description' => 'Coolify database', 'description' => 'Coolify database',
@ -94,6 +95,7 @@ public function addCoolifyDatabase()
'destination_type' => StandaloneDocker::class, 'destination_type' => StandaloneDocker::class,
'destination_id' => 0, 'destination_id' => 0,
]); ]);
$this->database->save();
$this->backup = ScheduledDatabaseBackup::create([ $this->backup = ScheduledDatabaseBackup::create([
'id' => 0, 'id' => 0,
'enabled' => true, 'enabled' => true,

View file

@ -203,6 +203,14 @@ class Application extends BaseModel
'restart_count', 'restart_count',
'last_restart_at', 'last_restart_at',
'last_restart_type', 'last_restart_type',
'uuid',
'environment_id',
'destination_id',
'destination_type',
'source_id',
'source_type',
'repository_project_id',
'private_key_id',
]; ];
protected $appends = ['server_status']; protected $appends = ['server_status'];
@ -262,7 +270,7 @@ protected static function booted()
} }
} }
if (count($payload) > 0) { if (count($payload) > 0) {
$application->forceFill($payload); $application->fill($payload);
} }
// Buildpack switching cleanup logic // Buildpack switching cleanup logic
@ -299,7 +307,7 @@ protected static function booted()
} }
}); });
static::created(function ($application) { static::created(function ($application) {
ApplicationSetting::forceCreate([ ApplicationSetting::create([
'application_id' => $application->id, 'application_id' => $application->id,
]); ]);
$application->compose_parsing_version = self::$parserVersion; $application->compose_parsing_version = self::$parserVersion;

View file

@ -44,6 +44,7 @@ class ApplicationDeploymentQueue extends Model
'application_id', 'application_id',
'deployment_uuid', 'deployment_uuid',
'pull_request_id', 'pull_request_id',
'docker_registry_image_tag',
'force_rebuild', 'force_rebuild',
'commit', 'commit',
'status', 'status',

View file

@ -11,6 +11,7 @@ class ApplicationPreview extends BaseModel
use SoftDeletes; use SoftDeletes;
protected $fillable = [ protected $fillable = [
'uuid',
'application_id', 'application_id',
'pull_request_id', 'pull_request_id',
'pull_request_html_url', 'pull_request_html_url',
@ -62,7 +63,7 @@ protected static function booted()
}); });
static::saving(function ($preview) { static::saving(function ($preview) {
if ($preview->isDirty('status')) { if ($preview->isDirty('status')) {
$preview->forceFill(['last_online_at' => now()]); $preview->last_online_at = now();
} }
}); });
} }

View file

@ -29,6 +29,7 @@ class ApplicationSetting extends Model
]; ];
protected $fillable = [ protected $fillable = [
'application_id',
'is_static', 'is_static',
'is_git_submodules_enabled', 'is_git_submodules_enabled',
'is_git_lfs_enabled', 'is_git_lfs_enabled',

View file

@ -5,6 +5,7 @@
class CloudProviderToken extends BaseModel class CloudProviderToken extends BaseModel
{ {
protected $fillable = [ protected $fillable = [
'team_id',
'provider', 'provider',
'token', 'token',
'name', 'name',

View file

@ -28,6 +28,8 @@ class Environment extends BaseModel
protected $fillable = [ protected $fillable = [
'name', 'name',
'description', 'description',
'project_id',
'uuid',
]; ];
protected static function booted() protected static function booted()

View file

@ -7,6 +7,8 @@
class GithubApp extends BaseModel class GithubApp extends BaseModel
{ {
protected $fillable = [ protected $fillable = [
'team_id',
'private_key_id',
'name', 'name',
'organization', 'organization',
'api_url', 'api_url',

View file

@ -27,6 +27,8 @@ class Project extends BaseModel
protected $fillable = [ protected $fillable = [
'name', 'name',
'description', 'description',
'team_id',
'uuid',
]; ];
/** /**
@ -51,10 +53,10 @@ public static function ownedByCurrentTeamCached()
protected static function booted() protected static function booted()
{ {
static::created(function ($project) { static::created(function ($project) {
ProjectSetting::forceCreate([ ProjectSetting::create([
'project_id' => $project->id, 'project_id' => $project->id,
]); ]);
Environment::forceCreate([ Environment::create([
'name' => 'production', 'name' => 'production',
'project_id' => $project->id, 'project_id' => $project->id,
'uuid' => (string) new Cuid2, 'uuid' => (string) new Cuid2,

View file

@ -6,7 +6,9 @@
class ProjectSetting extends Model class ProjectSetting extends Model
{ {
protected $fillable = []; protected $fillable = [
'project_id',
];
public function project() public function project()
{ {

View file

@ -9,6 +9,8 @@
class ScheduledDatabaseBackup extends BaseModel class ScheduledDatabaseBackup extends BaseModel
{ {
protected $fillable = [ protected $fillable = [
'uuid',
'team_id',
'description', 'description',
'enabled', 'enabled',
'save_s3', 'save_s3',

View file

@ -7,6 +7,8 @@
class ScheduledDatabaseBackupExecution extends BaseModel class ScheduledDatabaseBackupExecution extends BaseModel
{ {
protected $fillable = [ protected $fillable = [
'uuid',
'scheduled_database_backup_id',
'status', 'status',
'message', 'message',
'size', 'size',

View file

@ -30,12 +30,16 @@ class ScheduledTask extends BaseModel
use HasSafeStringAttribute; use HasSafeStringAttribute;
protected $fillable = [ protected $fillable = [
'uuid',
'enabled', 'enabled',
'name', 'name',
'command', 'command',
'frequency', 'frequency',
'container', 'container',
'timeout', 'timeout',
'team_id',
'application_id',
'service_id',
]; ];
public static function ownedByCurrentTeamAPI(int $teamId) public static function ownedByCurrentTeamAPI(int $teamId)

View file

@ -23,6 +23,7 @@
class ScheduledTaskExecution extends BaseModel class ScheduledTaskExecution extends BaseModel
{ {
protected $fillable = [ protected $fillable = [
'scheduled_task_id',
'status', 'status',
'message', 'message',
'finished_at', 'finished_at',

View file

@ -135,7 +135,7 @@ protected static function booted()
$payload['ip_previous'] = $server->getOriginal('ip'); $payload['ip_previous'] = $server->getOriginal('ip');
} }
} }
$server->forceFill($payload); $server->fill($payload);
}); });
static::saved(function ($server) { static::saved(function ($server) {
if ($server->wasChanged('private_key_id') || $server->privateKey?->isDirty()) { if ($server->wasChanged('private_key_id') || $server->privateKey?->isDirty()) {
@ -143,28 +143,28 @@ protected static function booted()
} }
}); });
static::created(function ($server) { static::created(function ($server) {
ServerSetting::forceCreate([ ServerSetting::create([
'server_id' => $server->id, 'server_id' => $server->id,
]); ]);
if ($server->id === 0) { if ($server->id === 0) {
if ($server->isSwarm()) { if ($server->isSwarm()) {
SwarmDocker::forceCreate([ (new SwarmDocker)->forceFill([
'id' => 0, 'id' => 0,
'name' => 'coolify', 'name' => 'coolify',
'network' => 'coolify-overlay', 'network' => 'coolify-overlay',
'server_id' => $server->id, 'server_id' => $server->id,
]); ])->save();
} else { } else {
StandaloneDocker::forceCreate([ (new StandaloneDocker)->forceFill([
'id' => 0, 'id' => 0,
'name' => 'coolify', 'name' => 'coolify',
'network' => 'coolify', 'network' => 'coolify',
'server_id' => $server->id, 'server_id' => $server->id,
]); ])->saveQuietly();
} }
} else { } else {
if ($server->isSwarm()) { if ($server->isSwarm()) {
SwarmDocker::forceCreate([ SwarmDocker::create([
'name' => 'coolify-overlay', 'name' => 'coolify-overlay',
'network' => 'coolify-overlay', 'network' => 'coolify-overlay',
'server_id' => $server->id, 'server_id' => $server->id,
@ -283,6 +283,7 @@ public static function flushIdentityMap(): void
'detected_traefik_version', 'detected_traefik_version',
'traefik_outdated_info', 'traefik_outdated_info',
'server_metadata', 'server_metadata',
'ip_previous',
]; ];
use HasSafeStringAttribute; use HasSafeStringAttribute;

View file

@ -54,6 +54,7 @@
class ServerSetting extends Model class ServerSetting extends Model
{ {
protected $fillable = [ protected $fillable = [
'server_id',
'is_swarm_manager', 'is_swarm_manager',
'is_jump_server', 'is_jump_server',
'is_build_server', 'is_build_server',

View file

@ -49,6 +49,7 @@ class Service extends BaseModel
private static $parserVersion = '5'; private static $parserVersion = '5';
protected $fillable = [ protected $fillable = [
'uuid',
'name', 'name',
'description', 'description',
'docker_compose_raw', 'docker_compose_raw',
@ -58,6 +59,10 @@ class Service extends BaseModel
'config_hash', 'config_hash',
'compose_parsing_version', 'compose_parsing_version',
'is_container_label_escape_enabled', 'is_container_label_escape_enabled',
'environment_id',
'server_id',
'destination_id',
'destination_type',
]; ];
protected $appends = ['server_status', 'status']; protected $appends = ['server_status', 'status'];

View file

@ -12,6 +12,7 @@ class ServiceApplication extends BaseModel
use HasFactory, SoftDeletes; use HasFactory, SoftDeletes;
protected $fillable = [ protected $fillable = [
'service_id',
'name', 'name',
'human_name', 'human_name',
'description', 'description',
@ -39,7 +40,7 @@ protected static function booted()
}); });
static::saving(function ($service) { static::saving(function ($service) {
if ($service->isDirty('status')) { if ($service->isDirty('status')) {
$service->forceFill(['last_online_at' => now()]); $service->last_online_at = now();
} }
}); });
} }

View file

@ -10,6 +10,7 @@ class ServiceDatabase extends BaseModel
use HasFactory, SoftDeletes; use HasFactory, SoftDeletes;
protected $fillable = [ protected $fillable = [
'service_id',
'name', 'name',
'human_name', 'human_name',
'description', 'description',
@ -44,7 +45,7 @@ protected static function booted()
}); });
static::saving(function ($service) { static::saving(function ($service) {
if ($service->isDirty('status')) { if ($service->isDirty('status')) {
$service->forceFill(['last_online_at' => now()]); $service->last_online_at = now();
} }
}); });
} }

View file

@ -14,6 +14,7 @@ class StandaloneClickhouse extends BaseModel
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes; use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
protected $fillable = [ protected $fillable = [
'uuid',
'name', 'name',
'description', 'description',
'clickhouse_admin_user', 'clickhouse_admin_user',
@ -40,6 +41,9 @@ class StandaloneClickhouse extends BaseModel
'public_port_timeout', 'public_port_timeout',
'custom_docker_run_options', 'custom_docker_run_options',
'clickhouse_db', 'clickhouse_db',
'destination_type',
'destination_id',
'environment_id',
]; ];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status']; protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
@ -71,7 +75,7 @@ protected static function booted()
}); });
static::saving(function ($database) { static::saving(function ($database) {
if ($database->isDirty('status')) { if ($database->isDirty('status')) {
$database->forceFill(['last_online_at' => now()]); $database->last_online_at = now();
} }
}); });
} }

View file

@ -13,6 +13,7 @@ class StandaloneDocker extends BaseModel
use HasSafeStringAttribute; use HasSafeStringAttribute;
protected $fillable = [ protected $fillable = [
'server_id',
'name', 'name',
'network', 'network',
]; ];

View file

@ -14,6 +14,7 @@ class StandaloneDragonfly extends BaseModel
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes; use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
protected $fillable = [ protected $fillable = [
'uuid',
'name', 'name',
'description', 'description',
'dragonfly_password', 'dragonfly_password',
@ -39,6 +40,9 @@ class StandaloneDragonfly extends BaseModel
'public_port_timeout', 'public_port_timeout',
'enable_ssl', 'enable_ssl',
'custom_docker_run_options', 'custom_docker_run_options',
'destination_type',
'destination_id',
'environment_id',
]; ];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status']; protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
@ -70,7 +74,7 @@ protected static function booted()
}); });
static::saving(function ($database) { static::saving(function ($database) {
if ($database->isDirty('status')) { if ($database->isDirty('status')) {
$database->forceFill(['last_online_at' => now()]); $database->last_online_at = now();
} }
}); });
} }

View file

@ -14,6 +14,7 @@ class StandaloneKeydb extends BaseModel
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes; use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
protected $fillable = [ protected $fillable = [
'uuid',
'name', 'name',
'description', 'description',
'keydb_password', 'keydb_password',
@ -40,6 +41,9 @@ class StandaloneKeydb extends BaseModel
'public_port_timeout', 'public_port_timeout',
'enable_ssl', 'enable_ssl',
'custom_docker_run_options', 'custom_docker_run_options',
'destination_type',
'destination_id',
'environment_id',
]; ];
protected $appends = ['internal_db_url', 'external_db_url', 'server_status']; protected $appends = ['internal_db_url', 'external_db_url', 'server_status'];
@ -71,7 +75,7 @@ protected static function booted()
}); });
static::saving(function ($database) { static::saving(function ($database) {
if ($database->isDirty('status')) { if ($database->isDirty('status')) {
$database->forceFill(['last_online_at' => now()]); $database->last_online_at = now();
} }
}); });
} }

View file

@ -15,6 +15,7 @@ class StandaloneMariadb extends BaseModel
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes; use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
protected $fillable = [ protected $fillable = [
'uuid',
'name', 'name',
'description', 'description',
'mariadb_root_password', 'mariadb_root_password',
@ -43,6 +44,9 @@ class StandaloneMariadb extends BaseModel
'enable_ssl', 'enable_ssl',
'is_log_drain_enabled', 'is_log_drain_enabled',
'custom_docker_run_options', 'custom_docker_run_options',
'destination_type',
'destination_id',
'environment_id',
]; ];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status']; protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
@ -74,7 +78,7 @@ protected static function booted()
}); });
static::saving(function ($database) { static::saving(function ($database) {
if ($database->isDirty('status')) { if ($database->isDirty('status')) {
$database->forceFill(['last_online_at' => now()]); $database->last_online_at = now();
} }
}); });
} }

View file

@ -14,6 +14,7 @@ class StandaloneMongodb extends BaseModel
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes; use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
protected $fillable = [ protected $fillable = [
'uuid',
'name', 'name',
'description', 'description',
'mongo_conf', 'mongo_conf',
@ -43,6 +44,9 @@ class StandaloneMongodb extends BaseModel
'is_log_drain_enabled', 'is_log_drain_enabled',
'is_include_timestamps', 'is_include_timestamps',
'custom_docker_run_options', 'custom_docker_run_options',
'destination_type',
'destination_id',
'environment_id',
]; ];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status']; protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
@ -80,7 +84,7 @@ protected static function booted()
}); });
static::saving(function ($database) { static::saving(function ($database) {
if ($database->isDirty('status')) { if ($database->isDirty('status')) {
$database->forceFill(['last_online_at' => now()]); $database->last_online_at = now();
} }
}); });
} }

View file

@ -14,6 +14,7 @@ class StandaloneMysql extends BaseModel
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes; use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
protected $fillable = [ protected $fillable = [
'uuid',
'name', 'name',
'description', 'description',
'mysql_root_password', 'mysql_root_password',
@ -44,6 +45,9 @@ class StandaloneMysql extends BaseModel
'is_log_drain_enabled', 'is_log_drain_enabled',
'is_include_timestamps', 'is_include_timestamps',
'custom_docker_run_options', 'custom_docker_run_options',
'destination_type',
'destination_id',
'environment_id',
]; ];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status']; protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
@ -76,7 +80,7 @@ protected static function booted()
}); });
static::saving(function ($database) { static::saving(function ($database) {
if ($database->isDirty('status')) { if ($database->isDirty('status')) {
$database->forceFill(['last_online_at' => now()]); $database->last_online_at = now();
} }
}); });
} }

View file

@ -14,6 +14,7 @@ class StandalonePostgresql extends BaseModel
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes; use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
protected $fillable = [ protected $fillable = [
'uuid',
'name', 'name',
'description', 'description',
'postgres_user', 'postgres_user',
@ -46,6 +47,9 @@ class StandalonePostgresql extends BaseModel
'is_log_drain_enabled', 'is_log_drain_enabled',
'is_include_timestamps', 'is_include_timestamps',
'custom_docker_run_options', 'custom_docker_run_options',
'destination_type',
'destination_id',
'environment_id',
]; ];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status']; protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
@ -92,7 +96,7 @@ protected static function booted()
}); });
static::saving(function ($database) { static::saving(function ($database) {
if ($database->isDirty('status')) { if ($database->isDirty('status')) {
$database->forceFill(['last_online_at' => now()]); $database->last_online_at = now();
} }
}); });
} }

View file

@ -14,6 +14,7 @@ class StandaloneRedis extends BaseModel
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes; use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
protected $fillable = [ protected $fillable = [
'uuid',
'name', 'name',
'description', 'description',
'redis_conf', 'redis_conf',
@ -39,6 +40,9 @@ class StandaloneRedis extends BaseModel
'is_log_drain_enabled', 'is_log_drain_enabled',
'is_include_timestamps', 'is_include_timestamps',
'custom_docker_run_options', 'custom_docker_run_options',
'destination_type',
'destination_id',
'environment_id',
]; ];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status']; protected $appends = ['internal_db_url', 'external_db_url', 'database_type', 'server_status'];
@ -69,7 +73,7 @@ protected static function booted()
}); });
static::saving(function ($database) { static::saving(function ($database) {
if ($database->isDirty('status')) { if ($database->isDirty('status')) {
$database->forceFill(['last_online_at' => now()]); $database->last_online_at = now();
} }
}); });

View file

@ -7,6 +7,7 @@
class Subscription extends Model class Subscription extends Model
{ {
protected $fillable = [ protected $fillable = [
'team_id',
'stripe_invoice_paid', 'stripe_invoice_paid',
'stripe_subscription_id', 'stripe_subscription_id',
'stripe_customer_id', 'stripe_customer_id',

View file

@ -7,6 +7,7 @@
class SwarmDocker extends BaseModel class SwarmDocker extends BaseModel
{ {
protected $fillable = [ protected $fillable = [
'server_id',
'name', 'name',
'network', 'network',
]; ];

View file

@ -10,6 +10,7 @@ class Tag extends BaseModel
protected $fillable = [ protected $fillable = [
'name', 'name',
'team_id',
]; ];
protected function customizeName($value) protected function customizeName($value)

View file

@ -49,6 +49,9 @@ class User extends Authenticatable implements SendsEmail
'password', 'password',
'force_password_reset', 'force_password_reset',
'marketing_emails', 'marketing_emails',
'pending_email',
'email_change_code',
'email_change_code_expires_at',
]; ];
protected $hidden = [ protected $hidden = [
@ -95,7 +98,8 @@ protected static function boot()
$team['id'] = 0; $team['id'] = 0;
$team['name'] = 'Root Team'; $team['name'] = 'Root Team';
} }
$new_team = Team::forceCreate($team); $new_team = (new Team)->forceFill($team);
$new_team->save();
$user->teams()->attach($new_team, ['role' => 'owner']); $user->teams()->attach($new_team, ['role' => 'owner']);
}); });
@ -198,7 +202,8 @@ public function recreate_personal_team()
$team['id'] = 0; $team['id'] = 0;
$team['name'] = 'Root Team'; $team['name'] = 'Root Team';
} }
$new_team = Team::forceCreate($team); $new_team = (new Team)->forceFill($team);
$new_team->save();
$this->teams()->attach($new_team, ['role' => 'owner']); $this->teams()->attach($new_team, ['role' => 'owner']);
return $new_team; return $new_team;
@ -409,7 +414,7 @@ public function requestEmailChange(string $newEmail): void
$expiryMinutes = config('constants.email_change.verification_code_expiry_minutes', 10); $expiryMinutes = config('constants.email_change.verification_code_expiry_minutes', 10);
$expiresAt = Carbon::now()->addMinutes($expiryMinutes); $expiresAt = Carbon::now()->addMinutes($expiryMinutes);
$this->forceFill([ $this->fill([
'pending_email' => $newEmail, 'pending_email' => $newEmail,
'email_change_code' => $code, 'email_change_code' => $code,
'email_change_code_expires_at' => $expiresAt, 'email_change_code_expires_at' => $expiresAt,

View file

@ -214,7 +214,7 @@ function clone_application(Application $source, $destination, array $overrides =
'updated_at', 'updated_at',
'additional_servers_count', 'additional_servers_count',
'additional_networks_count', 'additional_networks_count',
])->forceFill(array_merge([ ])->fill(array_merge([
'uuid' => $uuid, 'uuid' => $uuid,
'name' => $name, 'name' => $name,
'fqdn' => $url, 'fqdn' => $url,
@ -237,7 +237,7 @@ function clone_application(Application $source, $destination, array $overrides =
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->forceFill([ ])->fill([
'application_id' => $newApplication->id, 'application_id' => $newApplication->id,
]); ]);
$newApplicationSettings->save(); $newApplicationSettings->save();
@ -257,7 +257,7 @@ function clone_application(Application $source, $destination, array $overrides =
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->forceFill([ ])->fill([
'uuid' => (string) new Cuid2, 'uuid' => (string) new Cuid2,
'application_id' => $newApplication->id, 'application_id' => $newApplication->id,
'team_id' => currentTeam()->id, 'team_id' => currentTeam()->id,
@ -272,7 +272,7 @@ function clone_application(Application $source, $destination, array $overrides =
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->forceFill([ ])->fill([
'uuid' => (string) new Cuid2, 'uuid' => (string) new Cuid2,
'application_id' => $newApplication->id, 'application_id' => $newApplication->id,
'status' => 'exited', 'status' => 'exited',
@ -304,7 +304,7 @@ function clone_application(Application $source, $destination, array $overrides =
'created_at', 'created_at',
'updated_at', 'updated_at',
'uuid', 'uuid',
])->forceFill([ ])->fill([
'name' => $newName, 'name' => $newName,
'resource_id' => $newApplication->id, 'resource_id' => $newApplication->id,
]); ]);
@ -340,7 +340,7 @@ function clone_application(Application $source, $destination, array $overrides =
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->forceFill([ ])->fill([
'resource_id' => $newApplication->id, 'resource_id' => $newApplication->id,
]); ]);
$newStorage->save(); $newStorage->save();
@ -354,7 +354,7 @@ function clone_application(Application $source, $destination, array $overrides =
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->forceFill([ ])->fill([
'resourceable_id' => $newApplication->id, 'resourceable_id' => $newApplication->id,
'resourceable_type' => $newApplication->getMorphClass(), 'resourceable_type' => $newApplication->getMorphClass(),
'is_preview' => false, 'is_preview' => false,
@ -371,7 +371,7 @@ function clone_application(Application $source, $destination, array $overrides =
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->forceFill([ ])->fill([
'resourceable_id' => $newApplication->id, 'resourceable_id' => $newApplication->id,
'resourceable_type' => $newApplication->getMorphClass(), 'resourceable_type' => $newApplication->getMorphClass(),
'is_preview' => true, 'is_preview' => true,

View file

@ -1597,7 +1597,7 @@ function serviceParser(Service $resource): Collection
if ($databaseFound) { if ($databaseFound) {
$savedService = $databaseFound; $savedService = $databaseFound;
} else { } else {
$savedService = ServiceDatabase::forceCreate([ $savedService = ServiceDatabase::create([
'name' => $serviceName, 'name' => $serviceName,
'service_id' => $resource->id, 'service_id' => $resource->id,
]); ]);
@ -1607,7 +1607,7 @@ function serviceParser(Service $resource): Collection
if ($applicationFound) { if ($applicationFound) {
$savedService = $applicationFound; $savedService = $applicationFound;
} else { } else {
$savedService = ServiceApplication::forceCreate([ $savedService = ServiceApplication::create([
'name' => $serviceName, 'name' => $serviceName,
'service_id' => $resource->id, 'service_id' => $resource->id,
]); ]);

View file

@ -1919,7 +1919,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
// Create new serviceApplication or serviceDatabase // Create new serviceApplication or serviceDatabase
if ($isDatabase) { if ($isDatabase) {
if ($isNew) { if ($isNew) {
$savedService = ServiceDatabase::forceCreate([ $savedService = ServiceDatabase::create([
'name' => $serviceName, 'name' => $serviceName,
'image' => $image, 'image' => $image,
'service_id' => $resource->id, 'service_id' => $resource->id,
@ -1930,7 +1930,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
'service_id' => $resource->id, 'service_id' => $resource->id,
])->first(); ])->first();
if (is_null($savedService)) { if (is_null($savedService)) {
$savedService = ServiceDatabase::forceCreate([ $savedService = ServiceDatabase::create([
'name' => $serviceName, 'name' => $serviceName,
'image' => $image, 'image' => $image,
'service_id' => $resource->id, 'service_id' => $resource->id,
@ -1939,7 +1939,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
} }
} else { } else {
if ($isNew) { if ($isNew) {
$savedService = ServiceApplication::forceCreate([ $savedService = ServiceApplication::create([
'name' => $serviceName, 'name' => $serviceName,
'image' => $image, 'image' => $image,
'service_id' => $resource->id, 'service_id' => $resource->id,
@ -1950,7 +1950,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
'service_id' => $resource->id, 'service_id' => $resource->id,
])->first(); ])->first();
if (is_null($savedService)) { if (is_null($savedService)) {
$savedService = ServiceApplication::forceCreate([ $savedService = ServiceApplication::create([
'name' => $serviceName, 'name' => $serviceName,
'image' => $image, 'image' => $image,
'service_id' => $resource->id, 'service_id' => $resource->id,

View file

@ -5,11 +5,13 @@
<livewire:source.github.create /> <livewire:source.github.create />
</x-modal-input> </x-modal-input>
@if ($repositories->count() > 0) @if ($repositories->count() > 0)
<a target="_blank" class="flex hover:no-underline" href="{{ getInstallationPath($github_app) }}"> <x-forms.button wire:click.prevent="loadRepositories({{ $github_app->id }})">
<x-forms.button> Refresh Repository List
Change Repositories on GitHub </x-forms.button>
<x-external-link /> <a target="_blank" class="inline-flex items-center self-center gap-1 text-sm hover:underline dark:text-neutral-400"
</x-forms.button> href="{{ getInstallationPath($github_app) }}">
Change Repositories on GitHub
<x-external-link />
</a> </a>
@endif @endif
</div> </div>
@ -51,7 +53,10 @@
@endforeach @endforeach
</x-forms.datalist> </x-forms.datalist>
</div> </div>
<x-forms.button wire:click.prevent="loadBranches"> Load Repository </x-forms.button> <x-forms.button :showLoadingIndicator="false" wire:click.prevent="loadBranches" wire:target="loadBranches, selected_repository_id">
Load Repository
<x-loading-on-button wire:loading.delay wire:target="loadBranches, selected_repository_id" />
</x-forms.button>
</div> </div>
@else @else
<div>No repositories found. Check your GitHub App configuration.</div> <div>No repositories found. Check your GitHub App configuration.</div>

View file

@ -31,7 +31,7 @@
); );
}); });
$this->project = Project::forceCreate([ $this->project = Project::create([
'uuid' => (string) new Cuid2, 'uuid' => (string) new Cuid2,
'name' => 'test-project', 'name' => 'test-project',
'team_id' => $this->team->id, 'team_id' => $this->team->id,

View file

@ -6,7 +6,7 @@
describe('Application Rollback', function () { describe('Application Rollback', function () {
beforeEach(function () { beforeEach(function () {
$this->application = new Application; $this->application = new Application;
$this->application->forceFill([ $this->application->fill([
'uuid' => 'test-app-uuid', 'uuid' => 'test-app-uuid',
'git_commit_sha' => 'HEAD', 'git_commit_sha' => 'HEAD',
]); ]);

View file

@ -31,7 +31,7 @@
'redirect' => 'both', 'redirect' => 'both',
]); ]);
$this->application->settings->forceFill([ $this->application->settings->fill([
'is_container_label_readonly_enabled' => false, 'is_container_label_readonly_enabled' => false,
])->save(); ])->save();
@ -92,7 +92,7 @@
}); });
test('cloning application reassigns settings to the cloned application', function () { test('cloning application reassigns settings to the cloned application', function () {
$this->application->settings->forceFill([ $this->application->settings->fill([
'is_static' => true, 'is_static' => true,
'is_spa' => true, 'is_spa' => true,
'is_build_server_enabled' => true, 'is_build_server_enabled' => true,
@ -118,7 +118,7 @@
}); });
test('cloning application reassigns scheduled tasks and previews to the cloned application', function () { test('cloning application reassigns scheduled tasks and previews to the cloned application', function () {
$scheduledTask = ScheduledTask::forceCreate([ $scheduledTask = ScheduledTask::create([
'uuid' => 'scheduled-task-original', 'uuid' => 'scheduled-task-original',
'application_id' => $this->application->id, 'application_id' => $this->application->id,
'team_id' => $this->team->id, 'team_id' => $this->team->id,
@ -129,7 +129,7 @@
'timeout' => 120, 'timeout' => 120,
]); ]);
$preview = ApplicationPreview::forceCreate([ $preview = ApplicationPreview::create([
'uuid' => 'preview-original', 'uuid' => 'preview-original',
'application_id' => $this->application->id, 'application_id' => $this->application->id,
'pull_request_id' => 123, 'pull_request_id' => 123,

View file

@ -14,7 +14,7 @@
]), ]),
]); ]);
$preview = ApplicationPreview::forceCreate([ $preview = ApplicationPreview::create([
'application_id' => $application->id, 'application_id' => $application->id,
'pull_request_id' => 42, 'pull_request_id' => 42,
'pull_request_html_url' => 'https://github.com/example/repo/pull/42', 'pull_request_html_url' => 'https://github.com/example/repo/pull/42',
@ -39,7 +39,7 @@
]), ]),
]); ]);
$preview = ApplicationPreview::forceCreate([ $preview = ApplicationPreview::create([
'application_id' => $application->id, 'application_id' => $application->id,
'pull_request_id' => 7, 'pull_request_id' => 7,
'pull_request_html_url' => 'https://github.com/example/repo/pull/7', 'pull_request_html_url' => 'https://github.com/example/repo/pull/7',
@ -65,7 +65,7 @@
]), ]),
]); ]);
$preview = ApplicationPreview::forceCreate([ $preview = ApplicationPreview::create([
'application_id' => $application->id, 'application_id' => $application->id,
'pull_request_id' => 99, 'pull_request_id' => 99,
'pull_request_html_url' => 'https://github.com/example/repo/pull/99', 'pull_request_html_url' => 'https://github.com/example/repo/pull/99',

View file

@ -33,7 +33,7 @@
function createDatabase($context): StandalonePostgresql function createDatabase($context): StandalonePostgresql
{ {
return StandalonePostgresql::forceCreate([ return StandalonePostgresql::create([
'name' => 'test-postgres', 'name' => 'test-postgres',
'image' => 'postgres:15-alpine', 'image' => 'postgres:15-alpine',
'postgres_user' => 'postgres', 'postgres_user' => 'postgres',

View file

@ -33,7 +33,7 @@
describe('PATCH /api/v1/databases', function () { describe('PATCH /api/v1/databases', function () {
test('updates public_port_timeout on a postgresql database', function () { test('updates public_port_timeout on a postgresql database', function () {
$database = StandalonePostgresql::forceCreate([ $database = StandalonePostgresql::create([
'name' => 'test-postgres', 'name' => 'test-postgres',
'image' => 'postgres:15-alpine', 'image' => 'postgres:15-alpine',
'postgres_user' => 'postgres', 'postgres_user' => 'postgres',
@ -57,7 +57,7 @@
}); });
test('updates public_port_timeout on a redis database', function () { test('updates public_port_timeout on a redis database', function () {
$database = StandaloneRedis::forceCreate([ $database = StandaloneRedis::create([
'name' => 'test-redis', 'name' => 'test-redis',
'image' => 'redis:7', 'image' => 'redis:7',
'redis_password' => 'password', 'redis_password' => 'password',
@ -79,7 +79,7 @@
}); });
test('rejects invalid public_port_timeout value', function () { test('rejects invalid public_port_timeout value', function () {
$database = StandalonePostgresql::forceCreate([ $database = StandalonePostgresql::create([
'name' => 'test-postgres', 'name' => 'test-postgres',
'image' => 'postgres:15-alpine', 'image' => 'postgres:15-alpine',
'postgres_user' => 'postgres', 'postgres_user' => 'postgres',
@ -101,7 +101,7 @@
}); });
test('accepts null public_port_timeout', function () { test('accepts null public_port_timeout', function () {
$database = StandalonePostgresql::forceCreate([ $database = StandalonePostgresql::create([
'name' => 'test-postgres', 'name' => 'test-postgres',
'image' => 'postgres:15-alpine', 'image' => 'postgres:15-alpine',
'postgres_user' => 'postgres', 'postgres_user' => 'postgres',

View file

@ -52,7 +52,7 @@
$project = Project::factory()->create(['team_id' => $this->team->id]); $project = Project::factory()->create(['team_id' => $this->team->id]);
$environment = Environment::factory()->create(['project_id' => $project->id]); $environment = Environment::factory()->create(['project_id' => $project->id]);
$database = StandaloneMysql::forceCreate([ $database = StandaloneMysql::create([
'name' => 'test-mysql', 'name' => 'test-mysql',
'image' => 'mysql:8', 'image' => 'mysql:8',
'mysql_root_password' => 'password', 'mysql_root_password' => 'password',
@ -70,7 +70,7 @@
$component = Livewire::test(MysqlGeneral::class, ['database' => $database]) $component = Livewire::test(MysqlGeneral::class, ['database' => $database])
->assertDontSee('Database should be stopped to change this settings.'); ->assertDontSee('Database should be stopped to change this settings.');
$database->forceFill(['status' => 'running:healthy'])->save(); $database->fill(['status' => 'running:healthy'])->save();
$component->call('refresh') $component->call('refresh')
->assertSee('Database should be stopped to change this settings.'); ->assertSee('Database should be stopped to change this settings.');

View file

@ -69,7 +69,7 @@
describe('GetLogs Livewire action validation', function () { describe('GetLogs Livewire action validation', function () {
test('getLogs rejects invalid container name', function () { test('getLogs rejects invalid container name', function () {
// Make server functional by setting settings directly // Make server functional by setting settings directly
$this->server->settings->forceFill([ $this->server->settings->fill([
'is_reachable' => true, 'is_reachable' => true,
'is_usable' => true, 'is_usable' => true,
'force_disabled' => false, 'force_disabled' => false,
@ -100,7 +100,7 @@
}); });
test('downloadAllLogs returns empty for invalid container name', function () { test('downloadAllLogs returns empty for invalid container name', function () {
$this->server->settings->forceFill([ $this->server->settings->fill([
'is_reachable' => true, 'is_reachable' => true,
'is_usable' => true, 'is_usable' => true,
'force_disabled' => false, 'force_disabled' => false,

View file

@ -0,0 +1,164 @@
<?php
use App\Livewire\Project\New\GithubPrivateRepository;
use App\Models\GithubApp;
use App\Models\PrivateKey;
use App\Models\Team;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Livewire\Livewire;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->team = Team::factory()->create();
$this->user = User::factory()->create();
$this->team->members()->attach($this->user->id, ['role' => 'owner']);
$this->actingAs($this->user);
session(['currentTeam' => $this->team]);
$this->rsaKey = openssl_pkey_new([
'private_key_bits' => 2048,
'private_key_type' => OPENSSL_KEYTYPE_RSA,
]);
openssl_pkey_export($this->rsaKey, $pemKey);
$this->privateKey = PrivateKey::create([
'name' => 'Test Key',
'private_key' => $pemKey,
'team_id' => $this->team->id,
]);
$this->githubApp = GithubApp::create([
'name' => 'Test GitHub App',
'api_url' => 'https://api.github.com',
'html_url' => 'https://github.com',
'custom_user' => 'git',
'custom_port' => 22,
'app_id' => 12345,
'installation_id' => 67890,
'client_id' => 'test-client-id',
'client_secret' => 'test-client-secret',
'webhook_secret' => 'test-webhook-secret',
'private_key_id' => $this->privateKey->id,
'team_id' => $this->team->id,
'is_system_wide' => false,
]);
});
function fakeGithubHttp(array $repositories): void
{
Http::fake([
'https://api.github.com/zen' => Http::response('Keep it logically awesome.', 200, [
'Date' => now()->toRfc7231String(),
]),
'https://api.github.com/app/installations/67890/access_tokens' => Http::response([
'token' => 'fake-installation-token',
], 201),
'https://api.github.com/installation/repositories*' => Http::response([
'total_count' => count($repositories),
'repositories' => $repositories,
], 200),
]);
}
describe('GitHub Private Repository Component', function () {
test('loadRepositories fetches and displays repositories', function () {
$repos = [
['id' => 1, 'name' => 'alpha-repo', 'owner' => ['login' => 'testuser']],
['id' => 2, 'name' => 'beta-repo', 'owner' => ['login' => 'testuser']],
];
fakeGithubHttp($repos);
Livewire::test(GithubPrivateRepository::class, ['type' => 'private-gh-app'])
->assertSet('current_step', 'github_apps')
->call('loadRepositories', $this->githubApp->id)
->assertSet('current_step', 'repository')
->assertSet('total_repositories_count', 2)
->assertSet('selected_repository_id', 1);
});
test('loadRepositories can be called again to refresh the repository list', function () {
$initialRepos = [
['id' => 1, 'name' => 'alpha-repo', 'owner' => ['login' => 'testuser']],
];
$updatedRepos = [
['id' => 1, 'name' => 'alpha-repo', 'owner' => ['login' => 'testuser']],
['id' => 2, 'name' => 'beta-repo', 'owner' => ['login' => 'testuser']],
['id' => 3, 'name' => 'gamma-repo', 'owner' => ['login' => 'testuser']],
];
$callCount = 0;
Http::fake([
'https://api.github.com/zen' => Http::response('Keep it logically awesome.', 200, [
'Date' => now()->toRfc7231String(),
]),
'https://api.github.com/app/installations/67890/access_tokens' => Http::response([
'token' => 'fake-installation-token',
], 201),
'https://api.github.com/installation/repositories*' => function () use (&$callCount, $initialRepos, $updatedRepos) {
$callCount++;
$repos = $callCount === 1 ? $initialRepos : $updatedRepos;
return Http::response([
'total_count' => count($repos),
'repositories' => $repos,
], 200);
},
]);
$component = Livewire::test(GithubPrivateRepository::class, ['type' => 'private-gh-app'])
->call('loadRepositories', $this->githubApp->id)
->assertSet('total_repositories_count', 1);
// Simulate new repos becoming available after changing access on GitHub
$component
->call('loadRepositories', $this->githubApp->id)
->assertSet('total_repositories_count', 3)
->assertSet('current_step', 'repository');
});
test('loadRepositories resets branches when refreshing', function () {
$repos = [
['id' => 1, 'name' => 'alpha-repo', 'owner' => ['login' => 'testuser']],
];
fakeGithubHttp($repos);
$component = Livewire::test(GithubPrivateRepository::class, ['type' => 'private-gh-app'])
->call('loadRepositories', $this->githubApp->id);
// Manually set branches to simulate a previous branch load
$component->set('branches', collect([['name' => 'main'], ['name' => 'develop']]));
$component->set('total_branches_count', 2);
// Refresh repositories should reset branches
fakeGithubHttp($repos);
$component
->call('loadRepositories', $this->githubApp->id)
->assertSet('total_branches_count', 0)
->assertSet('branches', collect());
});
test('refresh button is visible when repositories are loaded', function () {
$repos = [
['id' => 1, 'name' => 'alpha-repo', 'owner' => ['login' => 'testuser']],
];
fakeGithubHttp($repos);
Livewire::test(GithubPrivateRepository::class, ['type' => 'private-gh-app'])
->call('loadRepositories', $this->githubApp->id)
->assertSee('Refresh Repository List');
});
test('refresh button is not visible before repositories are loaded', function () {
Livewire::test(GithubPrivateRepository::class, ['type' => 'private-gh-app'])
->assertDontSee('Refresh Repository List');
});
});

View file

@ -24,7 +24,7 @@
]); ]);
$destination = $server->standaloneDockers()->firstOrFail(); $destination = $server->standaloneDockers()->firstOrFail();
$application = Application::forceCreate([ $application = Application::create([
'name' => 'internal-app', 'name' => 'internal-app',
'git_repository' => 'https://github.com/coollabsio/coolify', 'git_repository' => 'https://github.com/coollabsio/coolify',
'git_branch' => 'main', 'git_branch' => 'main',
@ -57,7 +57,7 @@
]); ]);
$destination = $server->standaloneDockers()->firstOrFail(); $destination = $server->standaloneDockers()->firstOrFail();
$service = Service::forceCreate([ $service = Service::create([
'docker_compose_raw' => 'services: {}', 'docker_compose_raw' => 'services: {}',
'environment_id' => $environment->id, 'environment_id' => $environment->id,
'server_id' => $server->id, 'server_id' => $server->id,

File diff suppressed because it is too large Load diff

View file

@ -14,18 +14,18 @@
it('returns the correct team through the service relationship chain', function () { it('returns the correct team through the service relationship chain', function () {
$team = Team::factory()->create(); $team = Team::factory()->create();
$project = Project::forceCreate([ $project = Project::create([
'uuid' => (string) Str::uuid(), 'uuid' => (string) Str::uuid(),
'name' => 'Test Project', 'name' => 'Test Project',
'team_id' => $team->id, 'team_id' => $team->id,
]); ]);
$environment = Environment::forceCreate([ $environment = Environment::create([
'name' => 'test-env-'.Str::random(8), 'name' => 'test-env-'.Str::random(8),
'project_id' => $project->id, 'project_id' => $project->id,
]); ]);
$service = Service::forceCreate([ $service = Service::create([
'uuid' => (string) Str::uuid(), 'uuid' => (string) Str::uuid(),
'name' => 'supabase', 'name' => 'supabase',
'environment_id' => $environment->id, 'environment_id' => $environment->id,
@ -34,7 +34,7 @@
'docker_compose_raw' => 'version: "3"', 'docker_compose_raw' => 'version: "3"',
]); ]);
$serviceDatabase = ServiceDatabase::forceCreate([ $serviceDatabase = ServiceDatabase::create([
'uuid' => (string) Str::uuid(), 'uuid' => (string) Str::uuid(),
'name' => 'supabase-db', 'name' => 'supabase-db',
'service_id' => $service->id, 'service_id' => $service->id,
@ -47,18 +47,18 @@
it('returns the correct team for ServiceApplication through the service relationship chain', function () { it('returns the correct team for ServiceApplication through the service relationship chain', function () {
$team = Team::factory()->create(); $team = Team::factory()->create();
$project = Project::forceCreate([ $project = Project::create([
'uuid' => (string) Str::uuid(), 'uuid' => (string) Str::uuid(),
'name' => 'Test Project', 'name' => 'Test Project',
'team_id' => $team->id, 'team_id' => $team->id,
]); ]);
$environment = Environment::forceCreate([ $environment = Environment::create([
'name' => 'test-env-'.Str::random(8), 'name' => 'test-env-'.Str::random(8),
'project_id' => $project->id, 'project_id' => $project->id,
]); ]);
$service = Service::forceCreate([ $service = Service::create([
'uuid' => (string) Str::uuid(), 'uuid' => (string) Str::uuid(),
'name' => 'supabase', 'name' => 'supabase',
'environment_id' => $environment->id, 'environment_id' => $environment->id,
@ -67,7 +67,7 @@
'docker_compose_raw' => 'version: "3"', 'docker_compose_raw' => 'version: "3"',
]); ]);
$serviceApplication = ServiceApplication::forceCreate([ $serviceApplication = ServiceApplication::create([
'uuid' => (string) Str::uuid(), 'uuid' => (string) Str::uuid(),
'name' => 'supabase-studio', 'name' => 'supabase-studio',
'service_id' => $service->id, 'service_id' => $service->id,

View file

@ -49,7 +49,7 @@ function createTestApplication($context): Application
function createTestDatabase($context): StandalonePostgresql function createTestDatabase($context): StandalonePostgresql
{ {
return StandalonePostgresql::forceCreate([ return StandalonePostgresql::create([
'name' => 'test-postgres', 'name' => 'test-postgres',
'image' => 'postgres:15-alpine', 'image' => 'postgres:15-alpine',
'postgres_user' => 'postgres', 'postgres_user' => 'postgres',

View file

@ -1,12 +1,14 @@
<?php <?php
use App\Models\Application;
use App\Models\ApplicationSetting;
/** /**
* Security tests for git ref validation (GHSA-mw5w-2vvh-mgf4). * Security tests for git ref validation (GHSA-mw5w-2vvh-mgf4).
* *
* Ensures that git_commit_sha and related inputs are validated * Ensures that git_commit_sha and related inputs are validated
* to prevent OS command injection via shell metacharacters. * to prevent OS command injection via shell metacharacters.
*/ */
describe('validateGitRef', function () { describe('validateGitRef', function () {
test('accepts valid hex commit SHAs', function () { test('accepts valid hex commit SHAs', function () {
expect(validateGitRef('abc123def456'))->toBe('abc123def456'); expect(validateGitRef('abc123def456'))->toBe('abc123def456');
@ -93,31 +95,31 @@
describe('executeInDocker git log escaping', function () { describe('executeInDocker git log escaping', function () {
test('git log command escapes commit SHA to prevent injection', function () { test('git log command escapes commit SHA to prevent injection', function () {
$maliciousCommit = "HEAD'; id; #"; $maliciousCommit = "HEAD'; id; #";
$command = "cd /workdir && git log -1 ".escapeshellarg($maliciousCommit).' --pretty=%B'; $command = 'cd /workdir && git log -1 '.escapeshellarg($maliciousCommit).' --pretty=%B';
$result = executeInDocker('test-container', $command); $result = executeInDocker('test-container', $command);
// The malicious payload must not be able to break out of quoting // The malicious payload must not be able to break out of quoting
expect($result)->not->toContain("id;"); expect($result)->not->toContain('id;');
expect($result)->toContain("'HEAD'\\''"); expect($result)->toContain("'HEAD'\\''");
}); });
}); });
describe('buildGitCheckoutCommand escaping', function () { describe('buildGitCheckoutCommand escaping', function () {
test('checkout command escapes target to prevent injection', function () { test('checkout command escapes target to prevent injection', function () {
$app = new \App\Models\Application; $app = new Application;
$app->forceFill(['uuid' => 'test-uuid']); $app->fill(['uuid' => 'test-uuid']);
$settings = new \App\Models\ApplicationSetting; $settings = new ApplicationSetting;
$settings->is_git_submodules_enabled = false; $settings->is_git_submodules_enabled = false;
$app->setRelation('settings', $settings); $app->setRelation('settings', $settings);
$method = new \ReflectionMethod($app, 'buildGitCheckoutCommand'); $method = new ReflectionMethod($app, 'buildGitCheckoutCommand');
$result = $method->invoke($app, 'abc123'); $result = $method->invoke($app, 'abc123');
expect($result)->toContain("git checkout 'abc123'"); expect($result)->toContain("git checkout 'abc123'");
$result = $method->invoke($app, "abc'; id; #"); $result = $method->invoke($app, "abc'; id; #");
expect($result)->not->toContain("id;"); expect($result)->not->toContain('id;');
expect($result)->toContain("git checkout 'abc'"); expect($result)->toContain("git checkout 'abc'");
}); });
}); });

View file

@ -0,0 +1,76 @@
<?php
use App\Models\Application;
use App\Models\ApplicationDeploymentQueue;
use App\Models\ApplicationPreview;
use App\Models\ApplicationSetting;
use App\Models\CloudProviderToken;
use App\Models\Environment;
use App\Models\GithubApp;
use App\Models\Project;
use App\Models\ProjectSetting;
use App\Models\ScheduledDatabaseBackup;
use App\Models\ScheduledDatabaseBackupExecution;
use App\Models\ScheduledTask;
use App\Models\ScheduledTaskExecution;
use App\Models\Server;
use App\Models\ServerSetting;
use App\Models\Service;
use App\Models\ServiceApplication;
use App\Models\ServiceDatabase;
use App\Models\StandaloneClickhouse;
use App\Models\StandaloneDocker;
use App\Models\StandaloneDragonfly;
use App\Models\StandaloneKeydb;
use App\Models\StandaloneMariadb;
use App\Models\StandaloneMongodb;
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use App\Models\Subscription;
use App\Models\SwarmDocker;
use App\Models\Tag;
use App\Models\User;
it('keeps required mass-assignment attributes fillable for internal create flows', function (string $modelClass, array $expectedAttributes) {
$model = new $modelClass;
expect($model->getFillable())->toContain(...$expectedAttributes);
})->with([
// Relationship/ownership keys
[CloudProviderToken::class, ['team_id']],
[Tag::class, ['team_id']],
[Subscription::class, ['team_id']],
[ScheduledTaskExecution::class, ['scheduled_task_id']],
[ScheduledDatabaseBackupExecution::class, ['uuid', 'scheduled_database_backup_id']],
[ScheduledDatabaseBackup::class, ['uuid', 'team_id']],
[ScheduledTask::class, ['uuid', 'team_id', 'application_id', 'service_id']],
[ServiceDatabase::class, ['service_id']],
[ServiceApplication::class, ['service_id']],
[ApplicationDeploymentQueue::class, ['docker_registry_image_tag']],
[Project::class, ['team_id', 'uuid']],
[Environment::class, ['project_id', 'uuid']],
[ProjectSetting::class, ['project_id']],
[ApplicationSetting::class, ['application_id']],
[ServerSetting::class, ['server_id']],
[SwarmDocker::class, ['server_id']],
[StandaloneDocker::class, ['server_id']],
[User::class, ['pending_email', 'email_change_code', 'email_change_code_expires_at']],
[Server::class, ['ip_previous']],
[GithubApp::class, ['team_id', 'private_key_id']],
// Application/Service resource keys (including uuid for clone flows)
[Application::class, ['uuid', 'environment_id', 'destination_id', 'destination_type', 'source_id', 'source_type', 'repository_project_id', 'private_key_id']],
[ApplicationPreview::class, ['uuid', 'application_id']],
[Service::class, ['uuid', 'environment_id', 'server_id', 'destination_id', 'destination_type']],
// Standalone database resource keys (including uuid for clone flows)
[StandalonePostgresql::class, ['uuid', 'destination_type', 'destination_id', 'environment_id']],
[StandaloneMysql::class, ['uuid', 'destination_type', 'destination_id', 'environment_id']],
[StandaloneMariadb::class, ['uuid', 'destination_type', 'destination_id', 'environment_id']],
[StandaloneMongodb::class, ['uuid', 'destination_type', 'destination_id', 'environment_id']],
[StandaloneRedis::class, ['uuid', 'destination_type', 'destination_id', 'environment_id']],
[StandaloneKeydb::class, ['uuid', 'destination_type', 'destination_id', 'environment_id']],
[StandaloneDragonfly::class, ['uuid', 'destination_type', 'destination_id', 'environment_id']],
[StandaloneClickhouse::class, ['uuid', 'destination_type', 'destination_id', 'environment_id']],
]);

View file

@ -16,8 +16,8 @@
expect($parsersFile) expect($parsersFile)
->toContain("\$databaseFound = ServiceDatabase::where('name', \$serviceName)->where('service_id', \$resource->id)->first();") ->toContain("\$databaseFound = ServiceDatabase::where('name', \$serviceName)->where('service_id', \$resource->id)->first();")
->toContain("\$applicationFound = ServiceApplication::where('name', \$serviceName)->where('service_id', \$resource->id)->first();") ->toContain("\$applicationFound = ServiceApplication::where('name', \$serviceName)->where('service_id', \$resource->id)->first();")
->toContain("forceCreate([\n 'name' => \$serviceName,\n 'service_id' => \$resource->id,\n ]);") ->toContain("create([\n 'name' => \$serviceName,\n 'service_id' => \$resource->id,\n ]);")
->not->toContain("forceCreate([\n 'name' => \$serviceName,\n 'image' => \$image,\n 'service_id' => \$resource->id,\n ]);"); ->not->toContain("create([\n 'name' => \$serviceName,\n 'image' => \$image,\n 'service_id' => \$resource->id,\n ]);");
}); });
it('ensures service parser updates image after finding or creating service', function () { it('ensures service parser updates image after finding or creating service', function () {
@ -41,8 +41,8 @@
// The new code checks for null within the else block and creates only if needed // The new code checks for null within the else block and creates only if needed
expect($sharedFile) expect($sharedFile)
->toContain('if (is_null($savedService)) {') ->toContain('if (is_null($savedService)) {')
->toContain('$savedService = ServiceDatabase::forceCreate([') ->toContain('$savedService = ServiceDatabase::create([')
->toContain('$savedService = ServiceApplication::forceCreate(['); ->toContain('$savedService = ServiceApplication::create([');
}); });
it('verifies image update logic is present in parseDockerComposeFile', function () { it('verifies image update logic is present in parseDockerComposeFile', function () {

View file

@ -77,21 +77,21 @@
], ],
]); ]);
Project::forceCreate([ Project::create([
'uuid' => 'project-1', 'uuid' => 'project-1',
'name' => 'My first project', 'name' => 'My first project',
'description' => 'This is a test project in development', 'description' => 'This is a test project in development',
'team_id' => 0, 'team_id' => 0,
]); ]);
Project::forceCreate([ Project::create([
'uuid' => 'project-2', 'uuid' => 'project-2',
'name' => 'Production API', 'name' => 'Production API',
'description' => 'Backend services for production', 'description' => 'Backend services for production',
'team_id' => 0, 'team_id' => 0,
]); ]);
Project::forceCreate([ Project::create([
'uuid' => 'project-3', 'uuid' => 'project-3',
'name' => 'Staging Environment', 'name' => 'Staging Environment',
'description' => 'Staging and QA testing', 'description' => 'Staging and QA testing',