fix(models): replace forceFill/forceCreate with fill/create and add fillable guards

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.
This commit is contained in:
Andras Bacsai 2026-03-31 13:45:31 +02:00
parent 047aff1c82
commit 1a603a10ed
79 changed files with 1411 additions and 142 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

@ -191,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

@ -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()) {
@ -265,6 +265,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 = [
@ -409,7 +412,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

@ -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

@ -31,7 +31,7 @@
'team_id' => $this->team->id, 'team_id' => $this->team->id,
]); ]);
$this->githubApp = GithubApp::forceCreate([ $this->githubApp = GithubApp::create([
'name' => 'Test GitHub App', 'name' => 'Test GitHub App',
'api_url' => 'https://api.github.com', 'api_url' => 'https://api.github.com',
'html_url' => 'https://github.com', 'html_url' => 'https://github.com',

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',