diff --git a/app/Enums/BuildPackTypes.php b/app/Enums/BuildPackTypes.php
new file mode 100644
index 0000000000..d4fd505d22
--- /dev/null
+++ b/app/Enums/BuildPackTypes.php
@@ -0,0 +1,11 @@
+user()->id ?? null;
}
if (is_null($userId)) {
- throw new \Exception('User id is null');
+ throw new \RuntimeException('User id is null');
}
$this->userId = $userId;
}
diff --git a/app/Http/Controllers/Api/Applications.php b/app/Http/Controllers/Api/Applications.php
deleted file mode 100644
index 82fde140c8..0000000000
--- a/app/Http/Controllers/Api/Applications.php
+++ /dev/null
@@ -1,671 +0,0 @@
-get();
- $applications = collect();
- $applications->push($projects->pluck('applications')->flatten());
- $applications = $applications->flatten();
-
- return response()->json(serialize_api_response($applications));
- }
-
- public function application_by_uuid(Request $request)
- {
- $teamId = get_team_id_from_token();
- if (is_null($teamId)) {
- return invalid_token();
- }
- $uuid = $request->route('uuid');
- if (! $uuid) {
- return response()->json(['error' => 'UUID is required.'], 400);
- }
- $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
- if (! $application) {
- return response()->json(['error' => 'Application not found.'], 404);
- }
-
- return response()->json(serialize_api_response($application));
- }
-
- public function delete_by_uuid(Request $request)
- {
- ray()->clearAll();
- $teamId = get_team_id_from_token();
- $cleanup = $request->query->get('cleanup') ?? false;
- if (is_null($teamId)) {
- return invalid_token();
- }
-
- if ($request->collect()->count() == 0) {
- return response()->json([
- 'message' => 'Invalid request.',
- ], 400);
- }
- $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
-
- if (! $application) {
- return response()->json([
- 'success' => false,
- 'message' => 'Application not found',
- ], 404);
- }
- DeleteResourceJob::dispatch($application, $cleanup);
-
- return response()->json([
- 'success' => true,
- 'message' => 'Application deletion request queued.',
- ]);
- }
-
- public function update_by_uuid(Request $request)
- {
- ray()->clearAll();
- $teamId = get_team_id_from_token();
- if (is_null($teamId)) {
- return invalid_token();
- }
-
- if ($request->collect()->count() == 0) {
- return response()->json([
- 'message' => 'Invalid request.',
- ], 400);
- }
- $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
-
- if (! $application) {
- return response()->json([
- 'success' => false,
- 'message' => 'Application not found',
- ], 404);
- }
- $server = $application->destination->server;
- $allowedFields = ['name', 'description', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'redirect'];
-
- $validator = customApiValidator($request->all(), [
- 'name' => 'string|max:255',
- 'description' => 'string|nullable',
- 'domains' => 'string',
- 'git_repository' => 'string',
- 'git_branch' => 'string',
- 'git_commit_sha' => 'string',
- 'docker_registry_image_name' => 'string|nullable',
- 'docker_registry_image_tag' => 'string|nullable',
- 'build_pack' => 'string',
- 'static_image' => 'string',
- 'install_command' => 'string|nullable',
- 'build_command' => 'string|nullable',
- 'start_command' => 'string|nullable',
- 'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/',
- 'ports_mappings' => 'string|regex:/^(\d+:\d+)(,\d+:\d+)*$/|nullable',
- 'base_directory' => 'string|nullable',
- 'publish_directory' => 'string|nullable',
- 'health_check_enabled' => 'boolean',
- 'health_check_path' => 'string',
- 'health_check_port' => 'string|nullable',
- 'health_check_host' => 'string',
- 'health_check_method' => 'string',
- 'health_check_return_code' => 'numeric',
- 'health_check_scheme' => 'string',
- 'health_check_response_text' => 'string|nullable',
- 'health_check_interval' => 'numeric',
- 'health_check_timeout' => 'numeric',
- 'health_check_retries' => 'numeric',
- 'health_check_start_period' => 'numeric',
- 'limits_memory' => 'string',
- 'limits_memory_swap' => 'string',
- 'limits_memory_swappiness' => 'numeric',
- 'limits_memory_reservation' => 'string',
- 'limits_cpus' => 'string',
- 'limits_cpuset' => 'string|nullable',
- 'limits_cpu_shares' => 'numeric',
- 'custom_labels' => 'string|nullable',
- 'custom_docker_run_options' => 'string|nullable',
- 'post_deployment_command' => 'string|nullable',
- 'post_deployment_command_container' => 'string',
- 'pre_deployment_command' => 'string|nullable',
- 'pre_deployment_command_container' => 'string',
- 'watch_paths' => 'string|nullable',
- 'manual_webhook_secret_github' => 'string|nullable',
- 'manual_webhook_secret_gitlab' => 'string|nullable',
- 'manual_webhook_secret_bitbucket' => 'string|nullable',
- 'manual_webhook_secret_gitea' => 'string|nullable',
- 'docker_compose_location' => 'string',
- 'docker_compose' => 'string|nullable',
- 'docker_compose_raw' => 'string|nullable',
- // 'docker_compose_domains' => 'string|nullable', // must be like: "{\"api\":{\"domain\":\"http:\\/\\/b8sos8k.127.0.0.1.sslip.io\"}}"
- 'docker_compose_custom_start_command' => 'string|nullable',
- 'docker_compose_custom_build_command' => 'string|nullable',
- 'redirect' => Rule::enum(RedirectTypes::class),
- ]);
-
- // Validate ports_exposes
- if ($request->has('ports_exposes')) {
- $ports = explode(',', $request->ports_exposes);
- foreach ($ports as $port) {
- if (! is_numeric($port)) {
- return response()->json([
- 'message' => 'Validation failed.',
- 'errors' => [
- 'ports_exposes' => 'The ports_exposes should be a comma separated list of numbers.',
- ],
- ], 422);
- }
- }
- }
- // Validate ports_mappings
- if ($request->has('ports_mappings')) {
- $ports = [];
- foreach (explode(',', $request->ports_mappings) as $portMapping) {
- $port = explode(':', $portMapping);
- if (in_array($port[0], $ports)) {
- return response()->json([
- 'message' => 'Validation failed.',
- 'errors' => [
- 'ports_mappings' => 'The first number before : should be unique between mappings.',
- ],
- ], 422);
- }
- $ports[] = $port[0];
- }
- }
- // Validate custom_labels
- if ($request->has('custom_labels')) {
- if (! isBase64Encoded($request->custom_labels)) {
- return response()->json([
- 'message' => 'Validation failed.',
- 'errors' => [
- 'custom_labels' => 'The custom_labels should be base64 encoded.',
- ],
- ], 422);
- }
- $customLabels = base64_decode($request->custom_labels);
- if (mb_detect_encoding($customLabels, 'ASCII', true) === false) {
- return response()->json([
- 'message' => 'Validation failed.',
- 'errors' => [
- 'custom_labels' => 'The custom_labels should be base64 encoded.',
- ],
- ], 422);
-
- }
- }
- $extraFields = array_diff(array_keys($request->all()), $allowedFields);
- if ($validator->fails() || ! empty($extraFields)) {
- $errors = $validator->errors();
- if (! empty($extraFields)) {
- foreach ($extraFields as $field) {
- $errors->add($field, 'This field is not allowed.');
- }
- }
-
- return response()->json([
- 'message' => 'Validation failed.',
- 'errors' => $errors,
- ], 422);
- }
- if ($request->has('domains') && $server->isProxyShouldRun()) {
- $fqdn = $request->domains;
- $fqdn = str($fqdn)->replaceEnd(',', '')->trim();
- $fqdn = str($fqdn)->replaceStart(',', '')->trim();
- $errors = [];
- $fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) {
- if (filter_var($domain, FILTER_VALIDATE_URL) === false) {
- $errors[] = 'Invalid domain: '.$domain;
- }
-
- return str($domain)->trim()->lower();
- });
- if (count($errors) > 0) {
- return response()->json([
- 'message' => 'Validation failed.',
- 'errors' => $errors,
- ], 422);
- }
- $fqdn = $fqdn->unique()->implode(',');
- $application->fqdn = $fqdn;
- $customLabels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
- $application->custom_labels = base64_encode($customLabels);
- $request->offsetUnset('domains');
- }
- $application->fill($request->all());
- $application->save();
-
- return response()->json(serialize_api_response($application));
- }
-
- public function envs_by_uuid(Request $request)
- {
- ray()->clearAll();
- $teamId = get_team_id_from_token();
- if (is_null($teamId)) {
- return invalid_token();
- }
- $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
-
- if (! $application) {
- return response()->json([
- 'success' => false,
- 'message' => 'Application not found',
- ], 404);
- }
- $envs = $application->environment_variables->sortBy('id')->merge($application->environment_variables_preview->sortBy('id'));
-
- return response()->json(serialize_api_response($envs));
- }
-
- public function update_env_by_uuid(Request $request)
- {
- ray()->clearAll();
- $allowedFields = ['key', 'value', 'is_preview', 'is_build_time', 'is_literal'];
- $teamId = get_team_id_from_token();
-
- if (is_null($teamId)) {
- return invalid_token();
- }
- $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
-
- if (! $application) {
- return response()->json([
- 'success' => false,
- 'message' => 'Application not found',
- ], 404);
- }
- $validator = customApiValidator($request->all(), [
- 'key' => 'string|required',
- 'value' => 'string|nullable',
- 'is_preview' => 'boolean',
- 'is_build_time' => 'boolean',
- 'is_literal' => 'boolean',
- ]);
-
- $extraFields = array_diff(array_keys($request->all()), $allowedFields);
- if ($validator->fails() || ! empty($extraFields)) {
- $errors = $validator->errors();
- if (! empty($extraFields)) {
- foreach ($extraFields as $field) {
- $errors->add($field, 'This field is not allowed.');
- }
- }
-
- return response()->json([
- 'message' => 'Validation failed.',
- 'errors' => $errors,
- ], 422);
- }
- $is_preview = $request->is_preview ?? false;
- $is_build_time = $request->is_build_time ?? false;
- $is_literal = $request->is_literal ?? false;
- if ($is_preview) {
- $env = $application->environment_variables_preview->where('key', $request->key)->first();
- if ($env) {
- $env->value = $request->value;
- if ($env->is_build_time != $is_build_time) {
- $env->is_build_time = $is_build_time;
- }
- if ($env->is_literal != $is_literal) {
- $env->is_literal = $is_literal;
- }
- if ($env->is_preview != $is_preview) {
- $env->is_preview = $is_preview;
- }
- $env->save();
-
- return response()->json(serialize_api_response($env));
- } else {
- return response()->json([
- 'message' => 'Environment variable not found.',
- ], 404);
- }
- } else {
- $env = $application->environment_variables->where('key', $request->key)->first();
- if ($env) {
- $env->value = $request->value;
- if ($env->is_build_time != $is_build_time) {
- $env->is_build_time = $is_build_time;
- }
- if ($env->is_literal != $is_literal) {
- $env->is_literal = $is_literal;
- }
- if ($env->is_preview != $is_preview) {
- $env->is_preview = $is_preview;
- }
- $env->save();
-
- return response()->json(serialize_api_response($env));
- } else {
-
- return response()->json([
- 'message' => 'Environment variable not found.',
- ], 404);
-
- }
- }
-
- return response()->json([
- 'message' => 'Something went wrong.',
- ], 500);
-
- }
-
- public function create_bulk_envs(Request $request)
- {
- ray()->clearAll();
- $teamId = get_team_id_from_token();
-
- if (is_null($teamId)) {
- return invalid_token();
- }
- $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
-
- if (! $application) {
- return response()->json([
- 'success' => false,
- 'message' => 'Application not found',
- ], 404);
- }
-
- $bulk_data = $request->get('data');
- if (! $bulk_data) {
- return response()->json([
- 'message' => 'Bulk data is required.',
- ], 400);
- }
- $bulk_data = collect($bulk_data)->map(function ($item) {
- return collect($item)->only(['key', 'value', 'is_preview', 'is_build_time', 'is_literal']);
- });
- foreach ($bulk_data as $item) {
- $validator = customApiValidator($item, [
- 'key' => 'string|required',
- 'value' => 'string|nullable',
- 'is_preview' => 'boolean',
- 'is_build_time' => 'boolean',
- 'is_literal' => 'boolean',
- ]);
- if ($validator->fails()) {
- return response()->json([
- 'message' => 'Validation failed.',
- 'errors' => $validator->errors(),
- ], 422);
- }
- $is_preview = $item->get('is_preview') ?? false;
- $is_build_time = $item->get('is_build_time') ?? false;
- $is_literal = $item->get('is_literal') ?? false;
- if ($is_preview) {
- $env = $application->environment_variables_preview->where('key', $item->get('key'))->first();
- if ($env) {
- $env->value = $item->get('value');
- if ($env->is_build_time != $is_build_time) {
- $env->is_build_time = $is_build_time;
- }
- if ($env->is_literal != $is_literal) {
- $env->is_literal = $is_literal;
- }
- $env->save();
- } else {
- $env = $application->environment_variables()->create([
- 'key' => $item->get('key'),
- 'value' => $item->get('value'),
- 'is_preview' => $is_preview,
- 'is_build_time' => $is_build_time,
- 'is_literal' => $is_literal,
- ]);
- }
- } else {
- $env = $application->environment_variables->where('key', $item->get('key'))->first();
- if ($env) {
- $env->value = $item->get('value');
- if ($env->is_build_time != $is_build_time) {
- $env->is_build_time = $is_build_time;
- }
- if ($env->is_literal != $is_literal) {
- $env->is_literal = $is_literal;
- }
- $env->save();
- } else {
- $env = $application->environment_variables()->create([
- 'key' => $item->get('key'),
- 'value' => $item->get('value'),
- 'is_preview' => $is_preview,
- 'is_build_time' => $is_build_time,
- 'is_literal' => $is_literal,
- ]);
- }
- }
- }
-
- return response()->json([
- 'message' => 'Environments updated.',
- ]);
- }
-
- public function create_env(Request $request)
- {
- ray()->clearAll();
- $allowedFields = ['key', 'value', 'is_preview', 'is_build_time', 'is_literal'];
- $teamId = get_team_id_from_token();
-
- if (is_null($teamId)) {
- return invalid_token();
- }
- $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
-
- if (! $application) {
- return response()->json([
- 'success' => false,
- 'message' => 'Application not found',
- ], 404);
- }
- $validator = customApiValidator($request->all(), [
- 'key' => 'string|required',
- 'value' => 'string|nullable',
- 'is_preview' => 'boolean',
- 'is_build_time' => 'boolean',
- 'is_literal' => 'boolean',
- ]);
-
- $extraFields = array_diff(array_keys($request->all()), $allowedFields);
- if ($validator->fails() || ! empty($extraFields)) {
- $errors = $validator->errors();
- if (! empty($extraFields)) {
- foreach ($extraFields as $field) {
- $errors->add($field, 'This field is not allowed.');
- }
- }
-
- return response()->json([
- 'message' => 'Validation failed.',
- 'errors' => $errors,
- ], 422);
- }
- $is_preview = $request->is_preview ?? false;
- if ($is_preview) {
- $env = $application->environment_variables_preview->where('key', $request->key)->first();
- if ($env) {
- return response()->json([
- 'message' => 'Environment variable already exists. Use PATCH request to update it.',
- ], 409);
- } else {
- $env = $application->environment_variables()->create([
- 'key' => $request->key,
- 'value' => $request->value,
- 'is_preview' => $request->is_preview ?? false,
- 'is_build_time' => $request->is_build_time ?? false,
- 'is_literal' => $request->is_literal ?? false,
- ]);
-
- return response()->json(serialize_api_response($env))->setStatusCode(201);
- }
- } else {
- $env = $application->environment_variables->where('key', $request->key)->first();
- if ($env) {
- return response()->json([
- 'message' => 'Environment variable already exists. Use PATCH request to update it.',
- ], 409);
- } else {
- $env = $application->environment_variables()->create([
- 'key' => $request->key,
- 'value' => $request->value,
- 'is_preview' => $request->is_preview ?? false,
- 'is_build_time' => $request->is_build_time ?? false,
- 'is_literal' => $request->is_literal ?? false,
- ]);
-
- return response()->json(serialize_api_response($env))->setStatusCode(201);
-
- }
- }
-
- return response()->json([
- 'message' => 'Something went wrong.',
- ], 500);
-
- }
-
- public function delete_env_by_uuid(Request $request)
- {
- ray()->clearAll();
- $teamId = get_team_id_from_token();
- if (is_null($teamId)) {
- return invalid_token();
- }
- $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
-
- if (! $application) {
- return response()->json([
- 'success' => false,
- 'message' => 'Application not found.',
- ], 404);
- }
- $found_env = EnvironmentVariable::where('uuid', $request->env_uuid)->where('application_id', $application->id)->first();
- if (! $found_env) {
- return response()->json([
- 'success' => false,
- 'message' => 'Environment variable not found.',
- ], 404);
- }
- $found_env->delete();
-
- return response()->json([
- 'success' => true,
- 'message' => 'Environment variable deleted.',
- ]);
- }
-
- public function action_deploy(Request $request)
- {
- $teamId = get_team_id_from_token();
- if (is_null($teamId)) {
- return invalid_token();
- }
- $force = $request->query->get('force') ?? false;
- $instant_deploy = $request->query->get('instant_deploy') ?? false;
- $uuid = $request->route('uuid');
- if (! $uuid) {
- return response()->json(['error' => 'UUID is required.'], 400);
- }
- $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
- if (! $application) {
- return response()->json(['error' => 'Application not found.'], 404);
- }
-
- $deployment_uuid = new Cuid2(7);
-
- queue_application_deployment(
- application: $application,
- deployment_uuid: $deployment_uuid,
- force_rebuild: $force,
- is_api: true,
- no_questions_asked: $instant_deploy
- );
-
- return response()->json(
- [
- 'message' => 'Deployment request queued.',
- 'deployment_uuid' => $deployment_uuid->toString(),
- 'deployment_api_url' => base_url().'/api/v1/deployment/'.$deployment_uuid->toString(),
- ],
- 200
- );
- }
-
- public function action_stop(Request $request)
- {
- $teamId = get_team_id_from_token();
- if (is_null($teamId)) {
- return invalid_token();
- }
- $uuid = $request->route('uuid');
- $sync = $request->query->get('sync') ?? false;
- if (! $uuid) {
- return response()->json(['error' => 'UUID is required.'], 400);
- }
- $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
- if (! $application) {
- return response()->json(['error' => 'Application not found.'], 404);
- }
- if ($sync) {
- StopApplication::run($application);
-
- return response()->json(['message' => 'Stopped the application.'], 200);
- } else {
- StopApplication::dispatch($application);
-
- return response()->json(['message' => 'Stopping request queued.'], 200);
- }
- }
-
- public function action_restart(Request $request)
- {
- $teamId = get_team_id_from_token();
- if (is_null($teamId)) {
- return invalid_token();
- }
- $uuid = $request->route('uuid');
- if (! $uuid) {
- return response()->json(['error' => 'UUID is required.'], 400);
- }
- $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
- if (! $application) {
- return response()->json(['error' => 'Application not found.'], 404);
- }
-
- $deployment_uuid = new Cuid2(7);
-
- queue_application_deployment(
- application: $application,
- deployment_uuid: $deployment_uuid,
- restart_only: true,
- is_api: true,
- );
-
- return response()->json(
- [
- 'message' => 'Restart request queued.',
- 'deployment_uuid' => $deployment_uuid->toString(),
- 'deployment_api_url' => base_url().'/api/v1/deployment/'.$deployment_uuid->toString(),
- ],
- 200
- );
-
- }
-}
diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php
new file mode 100644
index 0000000000..e37de03786
--- /dev/null
+++ b/app/Http/Controllers/Api/ApplicationsController.php
@@ -0,0 +1,1173 @@
+get();
+ $applications = collect();
+ $applications->push($projects->pluck('applications')->flatten());
+ $applications = $applications->flatten();
+ $applications = $applications->map(function ($application) {
+ return serializeApiResponse($application);
+ });
+
+ return response()->json([
+ 'success' => true,
+ 'data' => $applications,
+ ]);
+ }
+
+ public function create_application(Request $request)
+ {
+ $allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile'];
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+
+ $return = validateIncomingRequest($request);
+ if ($return instanceof \Illuminate\Http\JsonResponse) {
+ return $return;
+ }
+ $validator = customApiValidator($request->all(), [
+ 'name' => 'string|max:255',
+ 'description' => 'string|nullable',
+ 'project_uuid' => 'string|required',
+ 'environment_name' => 'string|required',
+ 'server_uuid' => 'string|required',
+ 'destination_uuid' => 'string',
+ 'type' => ['required', Rule::enum(NewResourceTypes::class)],
+ ]);
+
+ $extraFields = array_diff(array_keys($request->all()), $allowedFields);
+ if ($validator->fails() || ! empty($extraFields)) {
+ $errors = $validator->errors();
+ if (! empty($extraFields)) {
+ foreach ($extraFields as $field) {
+ $errors->add($field, 'This field is not allowed.');
+ }
+ }
+
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Validation failed.',
+ 'errors' => $errors,
+ ], 422);
+ }
+
+ $serverUuid = $request->server_uuid;
+ $fqdn = $request->domains;
+ $type = $request->type;
+ $instantDeploy = $request->instant_deploy;
+ $githubAppUuid = $request->github_app_uuid;
+
+ $project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first();
+ if (! $project) {
+ return response()->json(['succes' => false, 'message' => 'Project not found.'], 404);
+ }
+ $environment = $project->environments()->where('name', $request->environment_name)->first();
+ if (! $environment) {
+ return response()->json(['success' => false, 'message' => 'Environment not found.'], 404);
+ }
+ $server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first();
+ if (! $server) {
+ return response()->json(['success' => false, 'message' => 'Server not found.'], 404);
+ }
+ $destinations = $server->destinations();
+ if ($destinations->count() == 0) {
+ return response()->json(['success' => false, 'message' => 'Server has no destinations.'], 400);
+ }
+ if ($destinations->count() > 1 && ! $request->has('destination_uuid')) {
+ return response()->json(['success' => false, 'message' => 'Server has multiple destinations and you do not set destination_uuid.'], 400);
+ }
+ $destination = $destinations->first();
+ if ($type === 'public') {
+ if (! $request->has('name')) {
+ $request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
+ }
+ $validator = customApiValidator($request->all(), [
+ sharedDataApplications(),
+ 'git_repository' => 'string|required',
+ 'git_branch' => 'string|required',
+ 'build_pack' => [Rule::enum(BuildPackTypes::class)],
+ 'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required',
+ ]);
+ if ($validator->fails()) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Validation failed.',
+ 'errors' => $validator->errors(),
+ ], 422);
+ }
+ $return = $this->validateDataApplications($request, $server);
+ if ($return instanceof \Illuminate\Http\JsonResponse) {
+ return $return;
+ }
+ $application = new Application();
+ removeUnnecessaryFieldsFromRequest($request);
+
+ $application->fill($request->all());
+
+ $application->fqdn = $fqdn;
+ $application->destination_id = $destination->id;
+ $application->destination_type = $destination->getMorphClass();
+ $application->environment_id = $environment->id;
+ $application->save();
+
+ if ($instantDeploy) {
+ $deployment_uuid = new Cuid2(7);
+
+ queue_application_deployment(
+ application: $application,
+ deployment_uuid: $deployment_uuid,
+ no_questions_asked: true,
+ is_api: true,
+ );
+ }
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($application),
+ ]);
+ } elseif ($type === 'private-gh-app') {
+ if (! $request->has('name')) {
+ $request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
+ }
+ $validator = customApiValidator($request->all(), [
+ sharedDataApplications(),
+ 'git_repository' => 'string|required',
+ 'git_branch' => 'string|required',
+ 'build_pack' => ['required', Rule::enum(BuildPackTypes::class)],
+ 'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required',
+ 'github_app_uuid' => 'string|required',
+ ]);
+ if ($validator->fails()) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Validation failed.',
+ 'errors' => $validator->errors(),
+ ], 422);
+ }
+ $return = $this->validateDataApplications($request, $server);
+ if ($return instanceof \Illuminate\Http\JsonResponse) {
+ return $return;
+ }
+ $githubApp = GithubApp::whereTeamId($teamId)->where('uuid', $githubAppUuid)->first();
+ if (! $githubApp) {
+ return response()->json(['success' => false, 'message' => 'Github App not found.'], 404);
+ }
+ $gitRepository = $request->git_repository;
+ if (str($gitRepository)->startsWith('http') || str($gitRepository)->contains('github.com')) {
+ $gitRepository = str($gitRepository)->replace('https://', '')->replace('http://', '')->replace('github.com/', '');
+ }
+ $application = new Application();
+ removeUnnecessaryFieldsFromRequest($request);
+
+ $application->fill($request->all());
+
+ $application->fqdn = $fqdn;
+ $application->git_repository = $gitRepository;
+ $application->destination_id = $destination->id;
+ $application->destination_type = $destination->getMorphClass();
+ $application->environment_id = $environment->id;
+ $application->source_type = $githubApp->getMorphClass();
+ $application->source_id = $githubApp->id;
+ $application->save();
+
+ if ($instantDeploy) {
+ $deployment_uuid = new Cuid2(7);
+
+ queue_application_deployment(
+ application: $application,
+ deployment_uuid: $deployment_uuid,
+ no_questions_asked: true,
+ is_api: true,
+ );
+ }
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($application),
+ ]);
+ } elseif ($type === 'private-deploy-key') {
+ if (! $request->has('name')) {
+ $request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
+ }
+ $validator = customApiValidator($request->all(), [
+ sharedDataApplications(),
+ 'git_repository' => 'string|required',
+ 'git_branch' => 'string|required',
+ 'build_pack' => ['required', Rule::enum(BuildPackTypes::class)],
+ 'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required',
+ 'private_key_uuid' => 'string|required',
+ ]);
+ if ($validator->fails()) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => $validator->errors(),
+ ], 422);
+ }
+ $return = $this->validateDataApplications($request, $server);
+ if ($return instanceof \Illuminate\Http\JsonResponse) {
+ return $return;
+ }
+ $privateKey = PrivateKey::whereTeamId($teamId)->where('uuid', $request->private_key_uuid)->first();
+ if (! $privateKey) {
+ return response()->json(['success' => false, 'message' => 'Private Key not found.'], 404);
+ }
+
+ $application = new Application();
+ removeUnnecessaryFieldsFromRequest($request);
+
+ $application->fill($request->all());
+ $application->fqdn = $fqdn;
+ $application->private_key_id = $privateKey->id;
+ $application->destination_id = $destination->id;
+ $application->destination_type = $destination->getMorphClass();
+ $application->environment_id = $environment->id;
+ $application->save();
+
+ if ($instantDeploy) {
+ $deployment_uuid = new Cuid2(7);
+
+ queue_application_deployment(
+ application: $application,
+ deployment_uuid: $deployment_uuid,
+ no_questions_asked: true,
+ is_api: true,
+ );
+ }
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($application),
+ ]);
+ } elseif ($type === 'dockerfile') {
+ if (! $request->has('name')) {
+ $request->offsetSet('name', 'dockerfile-'.new Cuid2(7));
+ }
+ $validator = customApiValidator($request->all(), [
+ sharedDataApplications(),
+ 'dockerfile' => 'string|required',
+ ]);
+ if ($validator->fails()) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Validation failed.',
+ 'errors' => $validator->errors(),
+ ], 422);
+ }
+ $return = $this->validateDataApplications($request, $server);
+ if ($return instanceof \Illuminate\Http\JsonResponse) {
+ return $return;
+ }
+ if (! isBase64Encoded($request->dockerfile)) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'dockerfile' => 'The dockerfile should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $dockerFile = base64_decode($request->dockerfile);
+ if (mb_detect_encoding($dockerFile, 'ASCII', true) === false) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'dockerfile' => 'The dockerfile should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $dockerFile = base64_decode($request->dockerfile);
+ removeUnnecessaryFieldsFromRequest($request);
+
+ $port = get_port_from_dockerfile($request->dockerfile);
+ if (! $port) {
+ $port = 80;
+ }
+
+ $application = new Application();
+ $application->fill($request->all());
+ $application->fqdn = $fqdn;
+ $application->ports_exposes = $port;
+ $application->build_pack = 'dockerfile';
+ $application->dockerfile = $dockerFile;
+ $application->destination_id = $destination->id;
+ $application->destination_type = $destination->getMorphClass();
+ $application->environment_id = $environment->id;
+
+ $application->git_repository = 'coollabsio/coolify';
+ $application->git_branch = 'main';
+ $application->save();
+
+ if ($instantDeploy) {
+ $deployment_uuid = new Cuid2(7);
+
+ queue_application_deployment(
+ application: $application,
+ deployment_uuid: $deployment_uuid,
+ no_questions_asked: true,
+ is_api: true,
+ );
+ }
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($application),
+ ]);
+ } elseif ($type === 'docker-image') {
+ if (! $request->has('name')) {
+ $request->offsetSet('name', 'docker-image-'.new Cuid2(7));
+ }
+ $validator = customApiValidator($request->all(), [
+ sharedDataApplications(),
+ 'docker_registry_image_name' => 'string|required',
+ 'docker_registry_image_tag' => 'string',
+ 'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required',
+ ]);
+ if ($validator->fails()) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Validation failed.',
+ 'errors' => $validator->errors(),
+ ], 422);
+ }
+ $return = $this->validateDataApplications($request, $server);
+ if ($return instanceof \Illuminate\Http\JsonResponse) {
+ return $return;
+ }
+ if (! $request->docker_registry_image_tag) {
+ $request->offsetSet('docker_registry_image_tag', 'latest');
+ }
+ $application = new Application();
+ removeUnnecessaryFieldsFromRequest($request);
+
+ $application->fill($request->all());
+ $application->fqdn = $fqdn;
+ $application->build_pack = 'dockerimage';
+ $application->destination_id = $destination->id;
+ $application->destination_type = $destination->getMorphClass();
+ $application->environment_id = $environment->id;
+
+ $application->git_repository = 'coollabsio/coolify';
+ $application->git_branch = 'main';
+ $application->save();
+
+ if ($instantDeploy) {
+ $deployment_uuid = new Cuid2(7);
+
+ queue_application_deployment(
+ application: $application,
+ deployment_uuid: $deployment_uuid,
+ no_questions_asked: true,
+ is_api: true,
+ );
+ }
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($application),
+ ]);
+ } elseif ($type === 'docker-compose-empty') {
+ if (! $request->has('name')) {
+ $request->offsetSet('name', 'service'.new Cuid2(7));
+ }
+ $validator = customApiValidator($request->all(), [
+ sharedDataApplications(),
+ 'docker_compose' => 'string|required',
+ 'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required',
+ ]);
+ if ($validator->fails()) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Validation failed.',
+ 'errors' => $validator->errors(),
+ ], 422);
+ }
+ $return = $this->validateDataApplications($request, $server);
+ if ($return instanceof \Illuminate\Http\JsonResponse) {
+ return $return;
+ }
+ if (! isBase64Encoded($request->docker_compose)) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'docker_compose' => 'The docker_compose should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $dockerCompose = base64_decode($request->docker_compose);
+ if (mb_detect_encoding($dockerCompose, 'ASCII', true) === false) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'docker_compose' => 'The docker_compose should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $dockerCompose = base64_decode($request->docker_compose);
+ $dockerComposeRaw = Yaml::dump(Yaml::parse($dockerCompose), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
+
+ // $isValid = validateComposeFile($dockerComposeRaw, $server_id);
+ // if ($isValid !== 'OK') {
+ // return $this->dispatch('error', "Invalid docker-compose file.\n$isValid");
+ // }
+
+ $service = new Service();
+ removeUnnecessaryFieldsFromRequest($request);
+ $service->name = $request->name;
+ $service->description = $request->description;
+ $service->docker_compose_raw = $dockerComposeRaw;
+ $service->environment_id = $environment->id;
+ $service->server_id = $server->id;
+ $service->destination_id = $destination->id;
+ $service->destination_type = $destination->getMorphClass();
+ $service->save();
+
+ $service->name = "service-$service->uuid";
+ $service->parse(isNew: true);
+ // if ($instantDeploy) {
+ // $deployment_uuid = new Cuid2(7);
+
+ // queue_application_deployment(
+ // application: $application,
+ // deployment_uuid: $deployment_uuid,
+ // no_questions_asked: true,
+ // is_api: true,
+ // );
+ // }
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($service),
+ ]);
+ }
+
+ return response()->json(['success' => false, 'message' => 'Invalid type.'], 400);
+
+ }
+
+ public function application_by_uuid(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $uuid = $request->route('uuid');
+ if (! $uuid) {
+ return response()->json(['success' => false, 'message' => 'UUID is required.'], 400);
+ }
+ $return = validateIncomingRequest($request);
+ if ($return instanceof \Illuminate\Http\JsonResponse) {
+ return $return;
+ }
+ $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
+ if (! $application) {
+ return response()->json(['success' => false, 'message' => 'Application not found.'], 404);
+ }
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($application),
+ ]);
+ }
+
+ public function delete_by_uuid(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ $cleanup = $request->query->get('cleanup') ?? false;
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+
+ if ($request->collect()->count() == 0) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Invalid request.',
+ ], 400);
+ }
+ $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
+
+ if (! $application) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Application not found',
+ ], 404);
+ }
+ DeleteResourceJob::dispatch($application, $cleanup);
+
+ return response()->json([
+ 'success' => true,
+ 'message' => 'Application deletion request queued.',
+ ]);
+ }
+
+ public function update_by_uuid(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+
+ if ($request->collect()->count() == 0) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Invalid request.',
+ ], 400);
+ }
+ $return = validateIncomingRequest($request);
+ if ($return instanceof \Illuminate\Http\JsonResponse) {
+ return $return;
+ }
+ $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
+
+ if (! $application) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Application not found',
+ ], 404);
+ }
+ $server = $application->destination->server;
+ $allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'redirect'];
+
+ $validator = customApiValidator($request->all(), [
+ sharedDataApplications(),
+ 'name' => 'string|max:255',
+ 'description' => 'string|nullable',
+ 'static_image' => 'string',
+ 'watch_paths' => 'string|nullable',
+ 'docker_compose_location' => 'string',
+ 'docker_compose' => 'string|nullable',
+ 'docker_compose_raw' => 'string|nullable',
+ // 'docker_compose_domains' => 'string|nullable', // must be like: "{\"api\":{\"domain\":\"http:\\/\\/b8sos8k.127.0.0.1.sslip.io\"}}"
+ 'docker_compose_custom_start_command' => 'string|nullable',
+ 'docker_compose_custom_build_command' => 'string|nullable',
+ ]);
+
+ // Validate ports_exposes
+ if ($request->has('ports_exposes')) {
+ $ports = explode(',', $request->ports_exposes);
+ foreach ($ports as $port) {
+ if (! is_numeric($port)) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'ports_exposes' => 'The ports_exposes should be a comma separated list of numbers.',
+ ],
+ ], 422);
+ }
+ }
+ }
+ $return = $this->validateDataApplications($request, $server);
+ if ($return instanceof \Illuminate\Http\JsonResponse) {
+ return $return;
+ }
+ $extraFields = array_diff(array_keys($request->all()), $allowedFields);
+ if ($validator->fails() || ! empty($extraFields)) {
+ $errors = $validator->errors();
+ if (! empty($extraFields)) {
+ foreach ($extraFields as $field) {
+ $errors->add($field, 'This field is not allowed.');
+ }
+ }
+
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Validation failed.',
+ 'errors' => $errors,
+ ], 422);
+ }
+ $domains = $request->domains;
+ if ($request->has('domains') && $server->isProxyShouldRun()) {
+ $fqdn = $request->domains;
+ $fqdn = str($fqdn)->replaceEnd(',', '')->trim();
+ $fqdn = str($fqdn)->replaceStart(',', '')->trim();
+ $errors = [];
+ $fqdn = $fqdn->unique()->implode(',');
+ $application->fqdn = $fqdn;
+ $customLabels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
+ $application->custom_labels = base64_encode($customLabels);
+ $request->offsetUnset('domains');
+ }
+
+ $data = $request->all();
+ data_set($data, 'fqdn', $domains);
+ $application->fill($data);
+ $application->save();
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($application),
+ ]);
+ }
+
+ public function envs_by_uuid(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $return = validateIncomingRequest($request);
+ if ($return instanceof \Illuminate\Http\JsonResponse) {
+ return $return;
+ }
+ $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
+
+ if (! $application) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Application not found',
+ ], 404);
+ }
+ $envs = $application->environment_variables->sortBy('id')->merge($application->environment_variables_preview->sortBy('id'));
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($envs),
+ ]);
+ }
+
+ public function update_env_by_uuid(Request $request)
+ {
+ $allowedFields = ['key', 'value', 'is_preview', 'is_build_time', 'is_literal'];
+ $teamId = getTeamIdFromToken();
+
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+
+ $return = validateIncomingRequest($request);
+ if ($return instanceof \Illuminate\Http\JsonResponse) {
+ return $return;
+ }
+ $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
+
+ if (! $application) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Application not found',
+ ], 404);
+ }
+ $validator = customApiValidator($request->all(), [
+ 'key' => 'string|required',
+ 'value' => 'string|nullable',
+ 'is_preview' => 'boolean',
+ 'is_build_time' => 'boolean',
+ 'is_literal' => 'boolean',
+ ]);
+
+ $extraFields = array_diff(array_keys($request->all()), $allowedFields);
+ if ($validator->fails() || ! empty($extraFields)) {
+ $errors = $validator->errors();
+ if (! empty($extraFields)) {
+ foreach ($extraFields as $field) {
+ $errors->add($field, 'This field is not allowed.');
+ }
+ }
+
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Validation failed.',
+ 'errors' => $errors,
+ ], 422);
+ }
+ $is_preview = $request->is_preview ?? false;
+ $is_build_time = $request->is_build_time ?? false;
+ $is_literal = $request->is_literal ?? false;
+ if ($is_preview) {
+ $env = $application->environment_variables_preview->where('key', $request->key)->first();
+ if ($env) {
+ $env->value = $request->value;
+ if ($env->is_build_time != $is_build_time) {
+ $env->is_build_time = $is_build_time;
+ }
+ if ($env->is_literal != $is_literal) {
+ $env->is_literal = $is_literal;
+ }
+ if ($env->is_preview != $is_preview) {
+ $env->is_preview = $is_preview;
+ }
+ $env->save();
+
+ return response()->json(serializeApiResponse($env));
+ } else {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Environment variable not found.',
+ ], 404);
+ }
+ } else {
+ $env = $application->environment_variables->where('key', $request->key)->first();
+ if ($env) {
+ $env->value = $request->value;
+ if ($env->is_build_time != $is_build_time) {
+ $env->is_build_time = $is_build_time;
+ }
+ if ($env->is_literal != $is_literal) {
+ $env->is_literal = $is_literal;
+ }
+ if ($env->is_preview != $is_preview) {
+ $env->is_preview = $is_preview;
+ }
+ $env->save();
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($env),
+ ]);
+ } else {
+
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Environment variable not found.',
+ ], 404);
+
+ }
+ }
+
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Something went wrong.',
+ ], 500);
+
+ }
+
+ public function create_bulk_envs(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+
+ $return = validateIncomingRequest($request);
+ if ($return instanceof \Illuminate\Http\JsonResponse) {
+ return $return;
+ }
+ $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
+
+ if (! $application) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Application not found',
+ ], 404);
+ }
+
+ $bulk_data = $request->get('data');
+ if (! $bulk_data) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Bulk data is required.',
+ ], 400);
+ }
+ $bulk_data = collect($bulk_data)->map(function ($item) {
+ return collect($item)->only(['key', 'value', 'is_preview', 'is_build_time', 'is_literal']);
+ });
+ foreach ($bulk_data as $item) {
+ $validator = customApiValidator($item, [
+ 'key' => 'string|required',
+ 'value' => 'string|nullable',
+ 'is_preview' => 'boolean',
+ 'is_build_time' => 'boolean',
+ 'is_literal' => 'boolean',
+ ]);
+ if ($validator->fails()) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Validation failed.',
+ 'errors' => $validator->errors(),
+ ], 422);
+ }
+ $is_preview = $item->get('is_preview') ?? false;
+ $is_build_time = $item->get('is_build_time') ?? false;
+ $is_literal = $item->get('is_literal') ?? false;
+ if ($is_preview) {
+ $env = $application->environment_variables_preview->where('key', $item->get('key'))->first();
+ if ($env) {
+ $env->value = $item->get('value');
+ if ($env->is_build_time != $is_build_time) {
+ $env->is_build_time = $is_build_time;
+ }
+ if ($env->is_literal != $is_literal) {
+ $env->is_literal = $is_literal;
+ }
+ $env->save();
+ } else {
+ $env = $application->environment_variables()->create([
+ 'key' => $item->get('key'),
+ 'value' => $item->get('value'),
+ 'is_preview' => $is_preview,
+ 'is_build_time' => $is_build_time,
+ 'is_literal' => $is_literal,
+ ]);
+ }
+ } else {
+ $env = $application->environment_variables->where('key', $item->get('key'))->first();
+ if ($env) {
+ $env->value = $item->get('value');
+ if ($env->is_build_time != $is_build_time) {
+ $env->is_build_time = $is_build_time;
+ }
+ if ($env->is_literal != $is_literal) {
+ $env->is_literal = $is_literal;
+ }
+ $env->save();
+ } else {
+ $env = $application->environment_variables()->create([
+ 'key' => $item->get('key'),
+ 'value' => $item->get('value'),
+ 'is_preview' => $is_preview,
+ 'is_build_time' => $is_build_time,
+ 'is_literal' => $is_literal,
+ ]);
+ }
+ }
+ }
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($env),
+ ]);
+ }
+
+ public function create_env(Request $request)
+ {
+ $allowedFields = ['key', 'value', 'is_preview', 'is_build_time', 'is_literal'];
+ $teamId = getTeamIdFromToken();
+
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
+
+ if (! $application) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Application not found',
+ ], 404);
+ }
+ $validator = customApiValidator($request->all(), [
+ 'key' => 'string|required',
+ 'value' => 'string|nullable',
+ 'is_preview' => 'boolean',
+ 'is_build_time' => 'boolean',
+ 'is_literal' => 'boolean',
+ ]);
+
+ $extraFields = array_diff(array_keys($request->all()), $allowedFields);
+ if ($validator->fails() || ! empty($extraFields)) {
+ $errors = $validator->errors();
+ if (! empty($extraFields)) {
+ foreach ($extraFields as $field) {
+ $errors->add($field, 'This field is not allowed.');
+ }
+ }
+
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Validation failed.',
+ 'errors' => $errors,
+ ], 422);
+ }
+ $is_preview = $request->is_preview ?? false;
+ if ($is_preview) {
+ $env = $application->environment_variables_preview->where('key', $request->key)->first();
+ if ($env) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Environment variable already exists. Use PATCH request to update it.',
+ ], 409);
+ } else {
+ $env = $application->environment_variables()->create([
+ 'key' => $request->key,
+ 'value' => $request->value,
+ 'is_preview' => $request->is_preview ?? false,
+ 'is_build_time' => $request->is_build_time ?? false,
+ 'is_literal' => $request->is_literal ?? false,
+ ]);
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($env),
+ ])->setStatusCode(201);
+ }
+ } else {
+ $env = $application->environment_variables->where('key', $request->key)->first();
+ if ($env) {
+ return response()->json([
+ 'message' => 'Environment variable already exists. Use PATCH request to update it.',
+ ], 409);
+ } else {
+ $env = $application->environment_variables()->create([
+ 'key' => $request->key,
+ 'value' => $request->value,
+ 'is_preview' => $request->is_preview ?? false,
+ 'is_build_time' => $request->is_build_time ?? false,
+ 'is_literal' => $request->is_literal ?? false,
+ ]);
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($env),
+ ])->setStatusCode(201);
+
+ }
+ }
+
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Something went wrong.',
+ ], 500);
+
+ }
+
+ public function delete_env_by_uuid(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
+
+ if (! $application) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Application not found.',
+ ], 404);
+ }
+ $found_env = EnvironmentVariable::where('uuid', $request->env_uuid)->where('application_id', $application->id)->first();
+ if (! $found_env) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Environment variable not found.',
+ ], 404);
+ }
+ $found_env->delete();
+
+ return response()->json([
+ 'success' => true,
+ 'message' => 'Environment variable deleted.',
+ ]);
+ }
+
+ public function action_deploy(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $force = $request->query->get('force') ?? false;
+ $instant_deploy = $request->query->get('instant_deploy') ?? false;
+ $uuid = $request->route('uuid');
+ if (! $uuid) {
+ return response()->json(['success' => false, 'message' => 'UUID is required.'], 400);
+ }
+ $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
+ if (! $application) {
+ return response()->json(['success' => false, 'message' => 'Application not found.'], 404);
+ }
+
+ $deployment_uuid = new Cuid2(7);
+
+ queue_application_deployment(
+ application: $application,
+ deployment_uuid: $deployment_uuid,
+ force_rebuild: $force,
+ is_api: true,
+ no_questions_asked: $instant_deploy
+ );
+
+ return response()->json(
+ [
+ 'success' => true,
+ 'message' => 'Deployment request queued.',
+ 'data' => [
+ 'deployment_uuid' => $deployment_uuid->toString(),
+ 'deployment_api_url' => base_url().'/api/v1/deployment/'.$deployment_uuid->toString(),
+ ],
+ ],
+ 200
+ );
+ }
+
+ public function action_stop(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $uuid = $request->route('uuid');
+ $sync = $request->query->get('sync') ?? false;
+ if (! $uuid) {
+ return response()->json(['success' => false, 'message' => 'UUID is required.'], 400);
+ }
+ $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
+ if (! $application) {
+ return response()->json(['success' => false, 'message' => 'Application not found.'], 404);
+ }
+ if ($sync) {
+ StopApplication::run($application);
+
+ return response()->json(
+ [
+ 'success' => true,
+ 'message' => 'Stopped the application.',
+ ],
+ );
+ } else {
+ StopApplication::dispatch($application);
+
+ return response()->json(
+ [
+ 'success' => true,
+ 'message' => 'Stopping request queued.',
+ ],
+ );
+ }
+ }
+
+ public function action_restart(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $uuid = $request->route('uuid');
+ if (! $uuid) {
+ return response()->json(['success' => false, 'message' => 'UUID is required.'], 400);
+ }
+ $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
+ if (! $application) {
+ return response()->json(['success' => false, 'message' => 'Application not found.'], 404);
+ }
+
+ $deployment_uuid = new Cuid2(7);
+
+ queue_application_deployment(
+ application: $application,
+ deployment_uuid: $deployment_uuid,
+ restart_only: true,
+ is_api: true,
+ );
+
+ return response()->json(
+ [
+ 'success' => true,
+ 'message' => 'Restart request queued.',
+ 'data' => [
+ 'deployment_uuid' => $deployment_uuid->toString(),
+ 'deployment_api_url' => base_url().'/api/v1/deployment/'.$deployment_uuid->toString(),
+ ],
+ ],
+ );
+
+ }
+
+ private function validateDataApplications(Request $request, Server $server)
+ {
+ $teamId = getTeamIdFromToken();
+
+ // Default build pack is nixpacks
+ if (! $request->has('build_pack')) {
+ $request->offsetSet('build_pack', 'nixpacks');
+ }
+
+ // Validate ports_mappings
+ if ($request->has('ports_mappings')) {
+ $ports = [];
+ foreach (explode(',', $request->ports_mappings) as $portMapping) {
+ $port = explode(':', $portMapping);
+ if (in_array($port[0], $ports)) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'ports_mappings' => 'The first number before : should be unique between mappings.',
+ ],
+ ], 422);
+ }
+ $ports[] = $port[0];
+ }
+ }
+ // Validate custom_labels
+ if ($request->has('custom_labels')) {
+ if (! isBase64Encoded($request->custom_labels)) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'custom_labels' => 'The custom_labels should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $customLabels = base64_decode($request->custom_labels);
+ if (mb_detect_encoding($customLabels, 'ASCII', true) === false) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'custom_labels' => 'The custom_labels should be base64 encoded.',
+ ],
+ ], 422);
+
+ }
+ }
+ if ($request->has('domains') && $server->isProxyShouldRun()) {
+ $fqdn = $request->domains;
+ $fqdn = str($fqdn)->replaceEnd(',', '')->trim();
+ $fqdn = str($fqdn)->replaceStart(',', '')->trim();
+ $errors = [];
+ $fqdn = str($fqdn)->trim()->explode(',')->map(function ($domain) use (&$errors) {
+ if (filter_var($domain, FILTER_VALIDATE_URL) === false) {
+ $errors[] = 'Invalid domain: '.$domain;
+ }
+
+ return str($domain)->trim()->lower();
+ });
+ if (count($errors) > 0) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Validation failed.',
+ 'errors' => $errors,
+ ], 422);
+ }
+ if (checkIfDomainIsAlreadyUsed($fqdn, $teamId)) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'domains' => 'One of the domain is already used.',
+ ],
+ ], 422);
+ }
+ }
+ }
+}
diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php
new file mode 100644
index 0000000000..36a5fffaff
--- /dev/null
+++ b/app/Http/Controllers/Api/DatabasesController.php
@@ -0,0 +1,259 @@
+get();
+ $databases = collect();
+ foreach ($projects as $project) {
+ $databases = $databases->merge($project->databases());
+ }
+ $databases = $databases->map(function ($database) {
+ return serializeApiResponse($database);
+ });
+
+ return response()->json([
+ 'success' => true,
+ 'data' => $databases,
+ ]);
+ }
+
+ public function database_by_uuid(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ if (! $request->uuid) {
+ return response()->json(['success' => false, 'message' => 'UUID is required.'], 404);
+ }
+ $database = queryDatabaseByUuidWithinTeam($request->uuid, $teamId);
+ if (! $database) {
+ return response()->json(['success' => false, 'message' => 'Database not found.'], 404);
+ }
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($database),
+ ]);
+ }
+
+ public function create_database(Request $request)
+ {
+ $allowedFields = ['type', 'name', 'description', 'image', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'postgres_user', 'postgres_password', 'postgres_db', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares'];
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+
+ $return = validateIncomingRequest($request);
+ if ($return instanceof \Illuminate\Http\JsonResponse) {
+ return $return;
+ }
+ $validator = customApiValidator($request->all(), [
+ 'type' => ['required', Rule::enum(NewDatabaseTypes::class)],
+ 'name' => 'string|max:255',
+ 'description' => 'string|nullable',
+ 'image' => 'string',
+ 'project_uuid' => 'string|required',
+ 'environment_name' => 'string|required',
+ 'server_uuid' => 'string|required',
+ 'destination_uuid' => 'string',
+ 'postgres_user' => 'string',
+ 'postgres_password' => 'string',
+ 'postgres_db' => 'string',
+ 'limits_memory' => 'string',
+ 'limits_memory_swap' => 'string',
+ 'limits_memory_swappiness' => 'numeric',
+ 'limits_memory_reservation' => 'string',
+ 'limits_cpus' => 'string',
+ 'limits_cpuset' => 'string|nullable',
+ 'limits_cpu_shares' => 'numeric',
+ 'instant_deploy' => 'boolean',
+ ]);
+
+ $extraFields = array_diff(array_keys($request->all()), $allowedFields);
+ if ($validator->fails() || ! empty($extraFields)) {
+ $errors = $validator->errors();
+ if (! empty($extraFields)) {
+ foreach ($extraFields as $field) {
+ $errors->add($field, 'This field is not allowed.');
+ }
+ }
+
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Validation failed.',
+ 'errors' => $errors,
+ ], 422);
+ }
+ $serverUuid = $request->server_uuid;
+ $instantDeploy = $request->instant_deploy ?? false;
+
+ $project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first();
+ if (! $project) {
+ return response()->json(['succes' => false, 'message' => 'Project not found.'], 404);
+ }
+ $environment = $project->environments()->where('name', $request->environment_name)->first();
+ if (! $environment) {
+ return response()->json(['success' => false, 'message' => 'Environment not found.'], 404);
+ }
+ $server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first();
+ if (! $server) {
+ return response()->json(['success' => false, 'message' => 'Server not found.'], 404);
+ }
+ $destinations = $server->destinations();
+ if ($destinations->count() == 0) {
+ return response()->json(['success' => false, 'message' => 'Server has no destinations.'], 400);
+ }
+ if ($destinations->count() > 1 && ! $request->has('destination_uuid')) {
+ return response()->json(['success' => false, 'message' => 'Server has multiple destinations and you do not set destination_uuid.'], 400);
+ }
+ $destination = $destinations->first();
+
+ if ($request->type === NewDatabaseTypes::POSTGRESQL->value) {
+ removeUnnecessaryFieldsFromRequest($request);
+ $database = create_standalone_postgresql($environment->id, $destination->uuid, $request->all());
+ if ($instantDeploy) {
+ StartPostgresql::dispatch($database);
+ }
+
+ return response()->json([
+ 'success' => true,
+ 'message' => 'Database starting queued.',
+ 'data' => serializeApiResponse($database),
+ ]);
+ } elseif ($request->type === NewDatabaseTypes::MARIADB->value) {
+ removeUnnecessaryFieldsFromRequest($request);
+ $database = create_standalone_mariadb($environment->id, $destination->uuid, $request->all());
+ if ($instantDeploy) {
+ StartMariadb::dispatch($database);
+ }
+
+ return response()->json([
+ 'success' => true,
+ 'message' => 'Database starting queued.',
+ 'data' => serializeApiResponse($database),
+ ]);
+ } elseif ($request->type === NewDatabaseTypes::MYSQL->value) {
+ removeUnnecessaryFieldsFromRequest($request);
+ $database = create_standalone_mysql($environment->id, $destination->uuid, $request->all());
+ if ($instantDeploy) {
+ StartMysql::dispatch($database);
+ }
+
+ return response()->json([
+ 'success' => true,
+ 'message' => 'Database starting queued.',
+ 'data' => serializeApiResponse($database),
+ ]);
+ } elseif ($request->type === NewDatabaseTypes::REDIS->value) {
+ removeUnnecessaryFieldsFromRequest($request);
+ $database = create_standalone_redis($environment->id, $destination->uuid, $request->all());
+ if ($instantDeploy) {
+ StartRedis::dispatch($database);
+ }
+
+ return response()->json([
+ 'success' => true,
+ 'message' => 'Database starting queued.',
+ 'data' => serializeApiResponse($database),
+ ]);
+ } elseif ($request->type === NewDatabaseTypes::DRAGONFLY->value) {
+ removeUnnecessaryFieldsFromRequest($request);
+ $database = create_standalone_dragonfly($environment->id, $destination->uuid, $request->all());
+ if ($instantDeploy) {
+ StartDragonfly::dispatch($database);
+ }
+
+ return response()->json([
+ 'success' => true,
+ 'message' => 'Database starting queued.',
+ 'data' => serializeApiResponse($database),
+ ]);
+ } elseif ($request->type === NewDatabaseTypes::KEYDB->value) {
+ removeUnnecessaryFieldsFromRequest($request);
+ $database = create_standalone_keydb($environment->id, $destination->uuid, $request->all());
+ if ($instantDeploy) {
+ StartKeydb::dispatch($database);
+ }
+
+ return response()->json([
+ 'success' => true,
+ 'message' => 'Database starting queued.',
+ 'data' => serializeApiResponse($database),
+ ]);
+ } elseif ($request->type === NewDatabaseTypes::CLICKHOUSE->value) {
+ removeUnnecessaryFieldsFromRequest($request);
+ $database = create_standalone_clickhouse($environment->id, $destination->uuid, $request->all());
+ if ($instantDeploy) {
+ StartClickhouse::dispatch($database);
+ }
+
+ return response()->json([
+ 'success' => true,
+ 'message' => 'Database starting queued.',
+ 'data' => serializeApiResponse($database),
+ ]);
+ } elseif ($request->type === NewDatabaseTypes::MONGODB->value) {
+ removeUnnecessaryFieldsFromRequest($request);
+ $database = create_standalone_mongodb($environment->id, $destination->uuid, $request->all());
+ if ($instantDeploy) {
+ StartMongodb::dispatch($database);
+ }
+
+ return response()->json([
+ 'success' => true,
+ 'message' => 'Database starting queued.',
+ 'data' => serializeApiResponse($database),
+ ]);
+ }
+
+ return response()->json(['success' => false, 'message' => 'Invalid database type requested.'], 400);
+ }
+
+ public function delete_by_uuid(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ if (! $request->uuid) {
+ return response()->json(['success' => false, 'message' => 'UUID is required.'], 404);
+ }
+ $database = queryDatabaseByUuidWithinTeam($request->uuid, $teamId);
+ if (! $database) {
+ return response()->json(['success' => false, 'message' => 'Database not found.'], 404);
+ }
+ StopDatabase::dispatch($database);
+ $database->delete();
+
+ return response()->json([
+ 'success' => true,
+ 'message' => 'Database deletion request queued.',
+ ]);
+ }
+}
diff --git a/app/Http/Controllers/Api/Deploy.php b/app/Http/Controllers/Api/DeployController.php
similarity index 73%
rename from app/Http/Controllers/Api/Deploy.php
rename to app/Http/Controllers/Api/DeployController.php
index d510970dd0..76e67548cf 100644
--- a/app/Http/Controllers/Api/Deploy.php
+++ b/app/Http/Controllers/Api/DeployController.php
@@ -18,13 +18,13 @@
use Illuminate\Http\Request;
use Visus\Cuid2\Cuid2;
-class Deploy extends Controller
+class DeployController extends Controller
{
public function deployments(Request $request)
{
- $teamId = get_team_id_from_token();
+ $teamId = getTeamIdFromToken();
if (is_null($teamId)) {
- return invalid_token();
+ return invalidTokenResponse();
}
$servers = Server::whereTeamId($teamId)->get();
$deployments_per_server = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('server_id', $servers->pluck('id'))->get([
@@ -38,39 +38,45 @@ public function deployments(Request $request)
'status',
])->sortBy('id')->toArray();
- return response()->json(serialize_api_response($deployments_per_server), 200);
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($deployments_per_server),
+ ]);
}
public function deployment_by_uuid(Request $request)
{
- $teamId = get_team_id_from_token();
+ $teamId = getTeamIdFromToken();
if (is_null($teamId)) {
- return invalid_token();
+ return invalidTokenResponse();
}
$uuid = $request->route('uuid');
if (! $uuid) {
- return response()->json(['message' => 'UUID is required.'], 400);
+ return response()->json(['success' => false, 'message' => 'UUID is required.'], 400);
}
$deployment = ApplicationDeploymentQueue::where('deployment_uuid', $uuid)->first();
if (! $deployment) {
- return response()->json(['message' => 'Deployment not found.'], 404);
+ return response()->json(['success' => false, 'message' => 'Deployment not found.'], 404);
}
- return response()->json(serialize_api_response($deployment->makeHidden('logs')), 200);
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($deployment->makeHidden('logs')),
+ ]);
}
public function deploy(Request $request)
{
- $teamId = get_team_id_from_token();
+ $teamId = getTeamIdFromToken();
$uuids = $request->query->get('uuid');
$tags = $request->query->get('tag');
$force = $request->query->get('force') ?? false;
if ($uuids && $tags) {
- return response()->json(['message' => 'You can only use uuid or tag, not both.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
+ return response()->json(['success' => false, 'message' => 'You can only use uuid or tag, not both.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
}
if (is_null($teamId)) {
- return invalid_token();
+ return invalidTokenResponse();
}
if ($tags) {
return $this->by_tags($tags, $teamId, $force);
@@ -78,7 +84,7 @@ public function deploy(Request $request)
return $this->by_uuids($uuids, $teamId, $force);
}
- return response()->json(['message' => 'You must provide uuid or tag.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
+ return response()->json(['success' => false, 'message' => 'You must provide uuid or tag.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
}
private function by_uuids(string $uuid, int $teamId, bool $force = false)
@@ -87,7 +93,7 @@ private function by_uuids(string $uuid, int $teamId, bool $force = false)
$uuids = collect(array_filter($uuids));
if (count($uuids) === 0) {
- return response()->json(['message' => 'No UUIDs provided.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
+ return response()->json(['success' => false, 'message' => 'No UUIDs provided.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
}
$deployments = collect();
$payload = collect();
@@ -96,19 +102,22 @@ private function by_uuids(string $uuid, int $teamId, bool $force = false)
if ($resource) {
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
if ($deployment_uuid) {
- $deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
+ $deployments->push(['success' => true, 'message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
} else {
- $deployments->push(['message' => $return_message, 'resource_uuid' => $uuid]);
+ $deployments->push(['success' => true, 'message' => $return_message, 'resource_uuid' => $uuid]);
}
}
}
if ($deployments->count() > 0) {
$payload->put('deployments', $deployments->toArray());
- return response()->json($payload->toArray(), 200);
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($payload->toArray()),
+ ]);
}
- return response()->json(['message' => 'No resources found.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404);
+ return response()->json(['success' => false, 'message' => 'No resources found.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404);
}
public function by_tags(string $tags, int $team_id, bool $force = false)
@@ -117,7 +126,7 @@ public function by_tags(string $tags, int $team_id, bool $force = false)
$tags = collect(array_filter($tags));
if (count($tags) === 0) {
- return response()->json(['message' => 'No TAGs provided.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
+ return response()->json(['success' => false, 'message' => 'No TAGs provided.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
}
$message = collect([]);
$deployments = collect();
@@ -153,10 +162,13 @@ public function by_tags(string $tags, int $team_id, bool $force = false)
$payload->put('details', $deployments->toArray());
}
- return response()->json($payload->toArray(), 200);
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($payload->toArray()),
+ ]);
}
- return response()->json(['message' => 'No resources found with this tag.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404);
+ return response()->json(['success' => false, 'message' => 'No resources found with this tag.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404);
}
public function deploy_resource($resource, bool $force = false): array
@@ -164,7 +176,7 @@ public function deploy_resource($resource, bool $force = false): array
$message = null;
$deployment_uuid = null;
if (gettype($resource) !== 'object') {
- return ['message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid];
+ return ['success' => false, 'message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid];
}
$type = $resource?->getMorphClass();
if ($type === 'App\Models\Application') {
@@ -228,6 +240,6 @@ public function deploy_resource($resource, bool $force = false): array
$message = "Service {$resource->name} started. It could take a while, be patient.";
}
- return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
+ return ['success' => true, 'message' => $message, 'deployment_uuid' => $deployment_uuid];
}
}
diff --git a/app/Http/Controllers/Api/EnvironmentVariables.php b/app/Http/Controllers/Api/EnvironmentVariablesController.php
similarity index 86%
rename from app/Http/Controllers/Api/EnvironmentVariables.php
rename to app/Http/Controllers/Api/EnvironmentVariablesController.php
index d788bdb0c4..c54656dc65 100644
--- a/app/Http/Controllers/Api/EnvironmentVariables.php
+++ b/app/Http/Controllers/Api/EnvironmentVariablesController.php
@@ -6,14 +6,13 @@
use App\Models\EnvironmentVariable;
use Illuminate\Http\Request;
-class EnvironmentVariables extends Controller
+class EnvironmentVariablesController extends Controller
{
public function delete_env_by_uuid(Request $request)
{
- ray()->clearAll();
- $teamId = get_team_id_from_token();
+ $teamId = getTeamIdFromToken();
if (is_null($teamId)) {
- return invalid_token();
+ return invalidTokenResponse();
}
$env = EnvironmentVariable::where('uuid', $request->env_uuid)->first();
if (! $env) {
diff --git a/app/Http/Controllers/Api/Project.php b/app/Http/Controllers/Api/Project.php
deleted file mode 100644
index baaf1eacb4..0000000000
--- a/app/Http/Controllers/Api/Project.php
+++ /dev/null
@@ -1,44 +0,0 @@
-select('id', 'name', 'uuid')->get();
-
- return response()->json($projects);
- }
-
- public function project_by_uuid(Request $request)
- {
- $teamId = get_team_id_from_token();
- if (is_null($teamId)) {
- return invalid_token();
- }
- $project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']);
-
- return response()->json($project);
- }
-
- public function environment_details(Request $request)
- {
- $teamId = get_team_id_from_token();
- if (is_null($teamId)) {
- return invalid_token();
- }
- $project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
- $environment = $project->environments()->whereName(request()->environment_name)->first()->load(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']);
-
- return response()->json($environment);
- }
-}
diff --git a/app/Http/Controllers/Api/ProjectController.php b/app/Http/Controllers/Api/ProjectController.php
new file mode 100644
index 0000000000..4721b48e12
--- /dev/null
+++ b/app/Http/Controllers/Api/ProjectController.php
@@ -0,0 +1,60 @@
+select('id', 'name', 'uuid')->get();
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($projects),
+ ]);
+ }
+
+ public function project_by_uuid(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $project = Project::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']);
+ if (! $project) {
+ return response()->json(['success' => false, 'message' => 'Project not found.'], 404);
+ }
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($project),
+ ]);
+ }
+
+ public function environment_details(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $project = Project::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
+ $environment = $project->environments()->whereName(request()->environment_name)->first();
+ if (! $environment) {
+ return response()->json(['success' => false, 'message' => 'Environment not found.'], 404);
+ }
+ $environment = $environment->load(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']);
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($environment),
+ ]);
+ }
+}
diff --git a/app/Http/Controllers/Api/Resources.php b/app/Http/Controllers/Api/ResourcesController.php
similarity index 80%
rename from app/Http/Controllers/Api/Resources.php
rename to app/Http/Controllers/Api/ResourcesController.php
index 0d538b62eb..47dfc67330 100644
--- a/app/Http/Controllers/Api/Resources.php
+++ b/app/Http/Controllers/Api/ResourcesController.php
@@ -6,13 +6,13 @@
use App\Models\Project;
use Illuminate\Http\Request;
-class Resources extends Controller
+class ResourcesController extends Controller
{
public function resources(Request $request)
{
- $teamId = get_team_id_from_token();
+ $teamId = getTeamIdFromToken();
if (is_null($teamId)) {
- return invalid_token();
+ return invalidTokenResponse();
}
$projects = Project::where('team_id', $teamId)->get();
$resources = collect();
@@ -34,6 +34,9 @@ public function resources(Request $request)
return $payload;
});
- return response()->json($resources);
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($resources),
+ ]);
}
}
diff --git a/app/Http/Controllers/Api/SecurityController.php b/app/Http/Controllers/Api/SecurityController.php
new file mode 100644
index 0000000000..51c6fee26f
--- /dev/null
+++ b/app/Http/Controllers/Api/SecurityController.php
@@ -0,0 +1,160 @@
+get();
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($keys),
+ ]);
+ }
+
+ public function key_by_uuid(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+
+ $key = PrivateKey::where('team_id', $teamId)->where('uuid', $request->uuid)->first();
+
+ if (is_null($key)) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Key not found.',
+ ], 404);
+ }
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($key),
+ ]);
+ }
+
+ public function create_key(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $return = validateIncomingRequest($request);
+ if ($return instanceof \Illuminate\Http\JsonResponse) {
+ return $return;
+ }
+ $validator = customApiValidator($request->all(), [
+ 'name' => 'string|max:255',
+ 'description' => 'string|max:255',
+ 'private_key' => 'required|string',
+ ]);
+
+ if ($validator->fails()) {
+ $errors = $validator->errors();
+
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Validation failed.',
+ 'errors' => $errors,
+ ], 422);
+ }
+ if (! $request->name) {
+ $request->offsetSet('name', generate_random_name());
+ }
+ if (! $request->description) {
+ $request->offsetSet('description', 'Created by Coolify via API');
+ }
+ $key = PrivateKey::create([
+ 'team_id' => $teamId,
+ 'name' => $request->name,
+ 'description' => $request->description,
+ 'private_key' => $request->private_key,
+ ]);
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($key),
+ ]);
+ }
+
+ public function update_key(Request $request)
+ {
+ $allowedFields = ['name', 'description', 'private_key'];
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $return = validateIncomingRequest($request);
+ if ($return instanceof \Illuminate\Http\JsonResponse) {
+ return $return;
+ }
+
+ $validator = customApiValidator($request->all(), [
+ 'name' => 'string|max:255',
+ 'description' => 'string|max:255',
+ 'private_key' => 'required|string',
+ ]);
+
+ $extraFields = array_diff(array_keys($request->all()), $allowedFields);
+ if ($validator->fails() || ! empty($extraFields)) {
+ $errors = $validator->errors();
+ if (! empty($extraFields)) {
+ foreach ($extraFields as $field) {
+ $errors->add($field, 'This field is not allowed.');
+ }
+ }
+
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Validation failed.',
+ 'errors' => $errors,
+ ], 422);
+ }
+ $foundKey = PrivateKey::where('team_id', $teamId)->where('uuid', $request->uuid)->first();
+ if (is_null($foundKey)) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Key not found.',
+ ], 404);
+ }
+ $foundKey->update($request->all());
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($foundKey),
+ ])->setStatusCode(201);
+ }
+
+ public function delete_key(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ if (! $request->uuid) {
+ return response()->json(['success' => false, 'message' => 'UUID is required.'], 422);
+ }
+
+ $key = PrivateKey::where('team_id', $teamId)->where('uuid', $request->uuid)->first();
+ if (is_null($key)) {
+ return response()->json(['success' => false, 'message' => 'Key not found.'], 404);
+ }
+ $key->delete();
+
+ return response()->json([
+ 'success' => true,
+ 'message' => 'Key deleted.',
+ ]);
+ }
+}
diff --git a/app/Http/Controllers/Api/Server.php b/app/Http/Controllers/Api/ServersController.php
similarity index 79%
rename from app/Http/Controllers/Api/Server.php
rename to app/Http/Controllers/Api/ServersController.php
index 1a58da7b06..4d9479b7cf 100644
--- a/app/Http/Controllers/Api/Server.php
+++ b/app/Http/Controllers/Api/ServersController.php
@@ -8,14 +8,15 @@
use App\Models\Project;
use App\Models\Server as ModelsServer;
use Illuminate\Http\Request;
+use Stringable;
-class Server extends Controller
+class ServersController extends Controller
{
public function servers(Request $request)
{
- $teamId = get_team_id_from_token();
+ $teamId = getTeamIdFromToken();
if (is_null($teamId)) {
- return invalid_token();
+ return invalidTokenResponse();
}
$servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port')->get()->load(['settings'])->map(function ($server) {
$server['is_reachable'] = $server->settings->is_reachable;
@@ -23,16 +24,22 @@ public function servers(Request $request)
return $server;
});
+ $servers = $servers->map(function ($server) {
+ return serializeApiResponse($server);
+ });
- return response()->json($servers);
+ return response()->json([
+ 'success' => true,
+ 'data' => $servers,
+ ]);
}
public function server_by_uuid(Request $request)
{
$with_resources = $request->query('resources');
- $teamId = get_team_id_from_token();
+ $teamId = getTeamIdFromToken();
if (is_null($teamId)) {
- return invalid_token();
+ return invalidTokenResponse();
}
$server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
if (is_null($server)) {
@@ -60,22 +67,25 @@ public function server_by_uuid(Request $request)
$server->load(['settings']);
}
- return response()->json($server);
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($server),
+ ]);
}
public function get_domains_by_server(Request $request)
{
- $teamId = get_team_id_from_token();
+ $teamId = getTeamIdFromToken();
if (is_null($teamId)) {
- return invalid_token();
+ return invalidTokenResponse();
}
- $uuid = $request->query->get('uuid');
+ $uuid = $request->get('uuid');
if ($uuid) {
$domains = Application::getDomainsByUuid($uuid);
return response()->json([
- 'uuid' => $uuid,
- 'domains' => $domains,
+ 'success' => true,
+ 'data' => serializeApiResponse($domains),
]);
}
$projects = Project::where('team_id', $teamId)->get();
@@ -86,8 +96,13 @@ public function get_domains_by_server(Request $request)
foreach ($applications as $application) {
$ip = $application->destination->server->ip;
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
- return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
+ $f = str($fqdn)->replace('http://', '')->replace('https://', '')->explode('/');
+
+ return str(str($f[0])->explode(':')[0]);
+ })->filter(function (Stringable $fqdn) {
+ return $fqdn->isNotEmpty();
});
+
if ($ip === 'host.docker.internal') {
if ($settings->public_ipv4) {
$domains->push([
@@ -122,7 +137,11 @@ public function get_domains_by_server(Request $request)
if ($service_applications->count() > 0) {
foreach ($service_applications as $application) {
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
- return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
+ $f = str($fqdn)->replace('http://', '')->replace('https://', '')->explode('/');
+
+ return str(str($f[0])->explode(':')[0]);
+ })->filter(function (Stringable $fqdn) {
+ return $fqdn->isNotEmpty();
});
if ($ip === 'host.docker.internal') {
if ($settings->public_ipv4) {
@@ -162,6 +181,9 @@ public function get_domains_by_server(Request $request)
];
})->values();
- return response()->json($domains);
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($domains),
+ ]);
}
}
diff --git a/app/Http/Controllers/Api/Team.php b/app/Http/Controllers/Api/Team.php
deleted file mode 100644
index c895f2c1b3..0000000000
--- a/app/Http/Controllers/Api/Team.php
+++ /dev/null
@@ -1,74 +0,0 @@
-user()->teams;
-
- return response()->json($teams);
- }
-
- public function team_by_id(Request $request)
- {
- $id = $request->id;
- $teamId = get_team_id_from_token();
- if (is_null($teamId)) {
- return invalid_token();
- }
- $teams = auth()->user()->teams;
- $team = $teams->where('id', $id)->first();
- if (is_null($team)) {
- return response()->json(['error' => 'Team not found.', 'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid'], 404);
- }
-
- return response()->json($team);
- }
-
- public function members_by_id(Request $request)
- {
- $id = $request->id;
- $teamId = get_team_id_from_token();
- if (is_null($teamId)) {
- return invalid_token();
- }
- $teams = auth()->user()->teams;
- $team = $teams->where('id', $id)->first();
- if (is_null($team)) {
- return response()->json(['error' => 'Team not found.', 'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid-members'], 404);
- }
-
- return response()->json($team->members);
- }
-
- public function current_team(Request $request)
- {
- $teamId = get_team_id_from_token();
- if (is_null($teamId)) {
- return invalid_token();
- }
- $team = auth()->user()->currentTeam();
-
- return response()->json($team);
- }
-
- public function current_team_members(Request $request)
- {
- $teamId = get_team_id_from_token();
- if (is_null($teamId)) {
- return invalid_token();
- }
- $team = auth()->user()->currentTeam();
-
- return response()->json($team->members);
- }
-}
diff --git a/app/Http/Controllers/Api/TeamController.php b/app/Http/Controllers/Api/TeamController.php
new file mode 100644
index 0000000000..a256e9caf9
--- /dev/null
+++ b/app/Http/Controllers/Api/TeamController.php
@@ -0,0 +1,89 @@
+user()->teams;
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($teams),
+ ]);
+ }
+
+ public function team_by_id(Request $request)
+ {
+ $id = $request->id;
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $teams = auth()->user()->teams;
+ $team = $teams->where('id', $id)->first();
+ if (is_null($team)) {
+ return response()->json(['success' => false, 'message' => 'Team not found.', 'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid'], 404);
+ }
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($team),
+ ]);
+ }
+
+ public function members_by_id(Request $request)
+ {
+ $id = $request->id;
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $teams = auth()->user()->teams;
+ $team = $teams->where('id', $id)->first();
+ if (is_null($team)) {
+ return response()->json(['success' => false, 'message' => 'Team not found.', 'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid-members'], 404);
+ }
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($team->members),
+ ]);
+ }
+
+ public function current_team(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $team = auth()->user()->currentTeam();
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($team),
+ ]);
+ }
+
+ public function current_team_members(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $team = auth()->user()->currentTeam();
+
+ return response()->json([
+ 'success' => true,
+ 'data' => serializeApiResponse($team->members),
+ ]);
+ }
+}
diff --git a/app/Http/Middleware/ApiAllowed.php b/app/Http/Middleware/ApiAllowed.php
new file mode 100644
index 0000000000..dc0a433e25
--- /dev/null
+++ b/app/Http/Middleware/ApiAllowed.php
@@ -0,0 +1,34 @@
+clearAll();
+ if (isCloud()) {
+ return $next($request);
+ }
+ $settings = InstanceSettings::get();
+ if ($settings->is_api_enabled === false) {
+ return response()->json(['success' => true, 'message' => 'API is disabled.'], 403);
+ }
+
+ if (! isDev()) {
+ if ($settings->allowed_ips) {
+ $allowedIps = explode(',', $settings->allowed_ips);
+ if (! in_array($request->ip(), $allowedIps)) {
+ return response()->json(['success' => true, 'message' => 'You are not allowed to access the API.'], 403);
+ }
+ }
+ }
+
+ return $next($request);
+ }
+}
diff --git a/app/Jobs/DockerCleanupJob.php b/app/Jobs/DockerCleanupJob.php
index e637fb6d47..785940ee63 100644
--- a/app/Jobs/DockerCleanupJob.php
+++ b/app/Jobs/DockerCleanupJob.php
@@ -35,9 +35,9 @@ public function handle(): void
return;
}
});
- if ($isInprogress) {
- throw new RuntimeException('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping...');
- }
+ // if ($isInprogress) {
+ // throw new RuntimeException('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping...');
+ // }
if (! $this->server->isFunctional()) {
return;
}
diff --git a/app/Livewire/Settings/Configuration.php b/app/Livewire/Settings/Configuration.php
index fe5a935e92..52f1185812 100644
--- a/app/Livewire/Settings/Configuration.php
+++ b/app/Livewire/Settings/Configuration.php
@@ -18,9 +18,10 @@ class Configuration extends Component
public bool $is_dns_validation_enabled;
- // public bool $next_channel;
protected string $dynamic_config_path;
+ public bool $is_api_enabled;
+
protected Server $server;
protected $rules = [
@@ -30,6 +31,7 @@ class Configuration extends Component
'settings.public_port_max' => 'required',
'settings.custom_dns_servers' => 'nullable',
'settings.instance_name' => 'nullable',
+ 'settings.allowed_ips' => 'nullable',
];
protected $validationAttributes = [
@@ -38,6 +40,7 @@ class Configuration extends Component
'settings.public_port_min' => 'Public port min',
'settings.public_port_max' => 'Public port max',
'settings.custom_dns_servers' => 'Custom DNS servers',
+ 'settings.allowed_ips' => 'Allowed IPs',
];
public function mount()
@@ -45,9 +48,9 @@ public function mount()
$this->do_not_track = $this->settings->do_not_track;
$this->is_auto_update_enabled = $this->settings->is_auto_update_enabled;
$this->is_registration_enabled = $this->settings->is_registration_enabled;
- // $this->next_channel = $this->settings->next_channel;
$this->is_dns_validation_enabled = $this->settings->is_dns_validation_enabled;
$this->dynamic_config_path = config('coolify.coolify_root_path').'/proxy/dynamic';
+ $this->is_api_enabled = $this->settings->is_api_enabled;
}
public function instantSave()
@@ -56,12 +59,7 @@ public function instantSave()
$this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
$this->settings->is_registration_enabled = $this->is_registration_enabled;
$this->settings->is_dns_validation_enabled = $this->is_dns_validation_enabled;
- // if ($this->next_channel) {
- // $this->settings->next_channel = false;
- // $this->next_channel = false;
- // } else {
- // $this->settings->next_channel = $this->next_channel;
- // }
+ $this->settings->is_api_enabled = $this->is_api_enabled;
$this->settings->save();
$this->dispatch('success', 'Settings updated!');
}
@@ -95,6 +93,13 @@ public function submit()
$this->settings->custom_dns_servers = $this->settings->custom_dns_servers->unique();
$this->settings->custom_dns_servers = $this->settings->custom_dns_servers->implode(',');
+ $this->settings->allowed_ips = str($this->settings->allowed_ips)->replaceEnd(',', '')->trim();
+ $this->settings->allowed_ips = str($this->settings->allowed_ips)->trim()->explode(',')->map(function ($ip) {
+ return str($ip)->trim();
+ });
+ $this->settings->allowed_ips = $this->settings->allowed_ips->unique();
+ $this->settings->allowed_ips = $this->settings->allowed_ips->implode(',');
+
$this->settings->save();
$this->server->setupDynamicProxyConfiguration();
if (! $error_show) {
diff --git a/app/Models/Environment.php b/app/Models/Environment.php
index b2bb51092a..fc19c134ff 100644
--- a/app/Models/Environment.php
+++ b/app/Models/Environment.php
@@ -27,6 +27,9 @@ public function isEmpty()
$this->redis()->count() == 0 &&
$this->postgresqls()->count() == 0 &&
$this->mysqls()->count() == 0 &&
+ $this->keydbs()->count() == 0 &&
+ $this->dragonflies()->count() == 0 &&
+ $this->clickhouses()->count() == 0 &&
$this->mariadbs()->count() == 0 &&
$this->mongodbs()->count() == 0 &&
$this->services()->count() == 0;
diff --git a/app/Models/GithubApp.php b/app/Models/GithubApp.php
index daf902daf3..66ecdd9670 100644
--- a/app/Models/GithubApp.php
+++ b/app/Models/GithubApp.php
@@ -20,6 +20,17 @@ class GithubApp extends BaseModel
'webhook_secret',
];
+ protected static function booted(): void
+ {
+ static::deleting(function (GithubApp $github_app) {
+ $applications_count = Application::where('source_id', $github_app->id)->count();
+ if ($applications_count > 0) {
+ throw new \Exception('You cannot delete this GitHub App because it is in use by '.$applications_count.' application(s). Delete them first.');
+ }
+ $github_app->privateKey()->delete();
+ });
+ }
+
public static function public()
{
return GithubApp::whereTeamId(currentTeam()->id)->whereisPublic(true)->whereNotNull('app_id')->get();
@@ -30,15 +41,9 @@ public static function private()
return GithubApp::whereTeamId(currentTeam()->id)->whereisPublic(false)->whereNotNull('app_id')->get();
}
- protected static function booted(): void
+ public function team()
{
- static::deleting(function (GithubApp $github_app) {
- $applications_count = Application::where('source_id', $github_app->id)->count();
- if ($applications_count > 0) {
- throw new \Exception('You cannot delete this GitHub App because it is in use by '.$applications_count.' application(s). Delete them first.');
- }
- $github_app->privateKey()->delete();
- });
+ return $this->belongsTo(Team::class);
}
public function applications()
diff --git a/app/Models/InstanceSettings.php b/app/Models/InstanceSettings.php
index 38f79ce751..bd3c41a1fb 100644
--- a/app/Models/InstanceSettings.php
+++ b/app/Models/InstanceSettings.php
@@ -17,6 +17,7 @@ class InstanceSettings extends Model implements SendsEmail
protected $casts = [
'resale_license' => 'encrypted',
'smtp_password' => 'encrypted',
+ 'allowed_ip_ranges' => 'array',
];
public function fqdn(): Attribute
diff --git a/app/Models/Server.php b/app/Models/Server.php
index b9cc556af1..05d790c609 100644
--- a/app/Models/Server.php
+++ b/app/Models/Server.php
@@ -496,16 +496,16 @@ public function checkServerApi()
public function checkSentinel()
{
- ray("Checking sentinel on server: {$this->name}");
+ // ray("Checking sentinel on server: {$this->name}");
if ($this->isSentinelEnabled()) {
$sentinel_found = instant_remote_process(['docker inspect coolify-sentinel'], $this, false);
$sentinel_found = json_decode($sentinel_found, true);
$status = data_get($sentinel_found, '0.State.Status', 'exited');
if ($status !== 'running') {
- ray('Sentinel is not running, starting it...');
+ // ray('Sentinel is not running, starting it...');
PullSentinelImageJob::dispatch($this);
} else {
- ray('Sentinel is running');
+ // ray('Sentinel is running');
}
}
}
diff --git a/app/Models/ServiceApplication.php b/app/Models/ServiceApplication.php
index 98c1cf4e75..6690f254ef 100644
--- a/app/Models/ServiceApplication.php
+++ b/app/Models/ServiceApplication.php
@@ -27,6 +27,11 @@ public function restart()
instant_remote_process(["docker restart {$container_id}"], $this->service->server);
}
+ public static function ownedByCurrentTeamAPI(int $teamId)
+ {
+ return ServiceApplication::whereRelation('service.environment.project.team', 'id', $teamId)->orderBy('name');
+ }
+
public function isLogDrainEnabled()
{
return data_get($this, 'is_log_drain_enabled', false);
diff --git a/app/Models/StandaloneClickhouse.php b/app/Models/StandaloneClickhouse.php
index e968db18d7..6732246501 100644
--- a/app/Models/StandaloneClickhouse.php
+++ b/app/Models/StandaloneClickhouse.php
@@ -13,6 +13,8 @@ class StandaloneClickhouse extends BaseModel
protected $guarded = [];
+ protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
+
protected $casts = [
'clickhouse_password' => 'encrypted',
];
@@ -178,17 +180,44 @@ public function team()
return data_get($this, 'environment.project.team');
}
+ public function databaseType(): Attribute
+ {
+ return new Attribute(
+ get: fn () => $this->type(),
+ );
+ }
+
public function type(): string
{
return 'standalone-clickhouse';
}
- public function get_db_url(bool $useInternal = false): string
+ protected function internalDbUrl(): Attribute
+ {
+ return new Attribute(
+ get: fn () => "clickhouse://{$this->clickhouse_user}:{$this->clickhouse_password}@{$this->uuid}:9000/{$this->clickhouse_db}",
+ );
+ }
+
+ protected function externalDbUrl(): Attribute
+ {
+ return new Attribute(
+ get: function () {
+ if ($this->is_public && $this->public_port) {
+ return "clickhouse://{$this->clickhouse_user}:{$this->clickhouse_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->clickhouse_db}";
+ }
+
+ return null;
+ }
+ );
+ }
+
+ public function get_db_url(bool $useInternal = false)
{
if ($this->is_public && ! $useInternal) {
- return "clickhouse://{$this->clickhouse_user}:{$this->clickhouse_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->clickhouse_db}";
+ return $this->externalDbUrl;
} else {
- return "clickhouse://{$this->clickhouse_user}:{$this->clickhouse_password}@{$this->uuid}:9000/{$this->clickhouse_db}";
+ return $this->internalDbUrl;
}
}
diff --git a/app/Models/StandaloneDragonfly.php b/app/Models/StandaloneDragonfly.php
index c6718acfe8..d78d656c1c 100644
--- a/app/Models/StandaloneDragonfly.php
+++ b/app/Models/StandaloneDragonfly.php
@@ -13,6 +13,8 @@ class StandaloneDragonfly extends BaseModel
protected $guarded = [];
+ protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
+
protected $casts = [
'dragonfly_password' => 'encrypted',
];
@@ -178,17 +180,44 @@ public function portsMappingsArray(): Attribute
);
}
+ public function databaseType(): Attribute
+ {
+ return new Attribute(
+ get: fn () => $this->type(),
+ );
+ }
+
public function type(): string
{
return 'standalone-dragonfly';
}
- public function get_db_url(bool $useInternal = false): string
+ protected function internalDbUrl(): Attribute
+ {
+ return new Attribute(
+ get: fn () => "redis://:{$this->dragonfly_password}@{$this->uuid}:6379/0",
+ );
+ }
+
+ protected function externalDbUrl(): Attribute
+ {
+ return new Attribute(
+ get: function () {
+ if ($this->is_public && $this->public_port) {
+ return "redis://:{$this->dragonfly_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
+ }
+
+ return null;
+ }
+ );
+ }
+
+ public function get_db_url(bool $useInternal = false)
{
if ($this->is_public && ! $useInternal) {
- return "redis://:{$this->dragonfly_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
+ return $this->externalDbUrl;
} else {
- return "redis://:{$this->dragonfly_password}@{$this->uuid}:6379/0";
+ return $this->internalDbUrl;
}
}
diff --git a/app/Models/StandaloneKeydb.php b/app/Models/StandaloneKeydb.php
index 142f960aab..7b71bd55f3 100644
--- a/app/Models/StandaloneKeydb.php
+++ b/app/Models/StandaloneKeydb.php
@@ -13,6 +13,8 @@ class StandaloneKeydb extends BaseModel
protected $guarded = [];
+ protected $appends = ['internal_db_url', 'external_db_url'];
+
protected $casts = [
'keydb_password' => 'encrypted',
];
@@ -178,17 +180,44 @@ public function portsMappingsArray(): Attribute
);
}
+ public function databaseType(): Attribute
+ {
+ return new Attribute(
+ get: fn () => $this->type(),
+ );
+ }
+
public function type(): string
{
return 'standalone-keydb';
}
- public function get_db_url(bool $useInternal = false): string
+ protected function internalDbUrl(): Attribute
+ {
+ return new Attribute(
+ get: fn () => "redis://{$this->keydb_password}@{$this->uuid}:6379/0",
+ );
+ }
+
+ protected function externalDbUrl(): Attribute
+ {
+ return new Attribute(
+ get: function () {
+ if ($this->is_public && $this->public_port) {
+ return "redis://{$this->keydb_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
+ }
+
+ return null;
+ }
+ );
+ }
+
+ public function get_db_url(bool $useInternal = false)
{
if ($this->is_public && ! $useInternal) {
- return "redis://{$this->keydb_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
+ return $this->externalDbUrl;
} else {
- return "redis://{$this->keydb_password}@{$this->uuid}:6379/0";
+ return $this->internalDbUrl;
}
}
diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php
index 7e6d2e0d1e..00df4fe719 100644
--- a/app/Models/StandaloneMariadb.php
+++ b/app/Models/StandaloneMariadb.php
@@ -13,6 +13,8 @@ class StandaloneMariadb extends BaseModel
protected $guarded = [];
+ protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
+
protected $casts = [
'mariadb_password' => 'encrypted',
];
@@ -161,6 +163,13 @@ public function isLogDrainEnabled()
return data_get($this, 'is_log_drain_enabled', false);
}
+ public function databaseType(): Attribute
+ {
+ return new Attribute(
+ get: fn () => $this->type(),
+ );
+ }
+
public function type(): string
{
return 'standalone-mariadb';
@@ -183,12 +192,32 @@ public function portsMappingsArray(): Attribute
);
}
- public function get_db_url(bool $useInternal = false): string
+ protected function internalDbUrl(): Attribute
+ {
+ return new Attribute(
+ get: fn () => "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->uuid}:3306/{$this->mariadb_database}",
+ );
+ }
+
+ protected function externalDbUrl(): Attribute
+ {
+ return new Attribute(
+ get: function () {
+ if ($this->is_public && $this->public_port) {
+ return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mariadb_database}";
+ }
+
+ return null;
+ }
+ );
+ }
+
+ public function get_db_url(bool $useInternal = false)
{
if ($this->is_public && ! $useInternal) {
- return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mariadb_database}";
+ return $this->externalDbUrl;
} else {
- return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->uuid}:3306/{$this->mariadb_database}";
+ return $this->internalDbUrl;
}
}
diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php
index df895bb34f..0863522a8f 100644
--- a/app/Models/StandaloneMongodb.php
+++ b/app/Models/StandaloneMongodb.php
@@ -13,6 +13,8 @@ class StandaloneMongodb extends BaseModel
protected $guarded = [];
+ protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
+
protected static function booted()
{
static::created(function ($database) {
@@ -198,17 +200,44 @@ public function portsMappingsArray(): Attribute
);
}
+ public function databaseType(): Attribute
+ {
+ return new Attribute(
+ get: fn () => $this->type(),
+ );
+ }
+
public function type(): string
{
return 'standalone-mongodb';
}
+ protected function internalDbUrl(): Attribute
+ {
+ return new Attribute(
+ get: fn () => "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->uuid}:27017/?directConnection=true",
+ );
+ }
+
+ protected function externalDbUrl(): Attribute
+ {
+ return new Attribute(
+ get: function () {
+ if ($this->is_public && $this->public_port) {
+ return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->destination->server->getIp}:{$this->public_port}/?directConnection=true";
+ }
+
+ return null;
+ }
+ );
+ }
+
public function get_db_url(bool $useInternal = false)
{
if ($this->is_public && ! $useInternal) {
- return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->destination->server->getIp}:{$this->public_port}/?directConnection=true";
+ return $this->externalDbUrl;
} else {
- return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->uuid}:27017/?directConnection=true";
+ return $this->internalDbUrl;
}
}
diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php
index bd160f8772..79e7c37fa3 100644
--- a/app/Models/StandaloneMysql.php
+++ b/app/Models/StandaloneMysql.php
@@ -13,6 +13,8 @@ class StandaloneMysql extends BaseModel
protected $guarded = [];
+ protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
+
protected $casts = [
'mysql_password' => 'encrypted',
'mysql_root_password' => 'encrypted',
@@ -157,6 +159,13 @@ public function link()
return null;
}
+ public function databaseType(): Attribute
+ {
+ return new Attribute(
+ get: fn () => $this->type(),
+ );
+ }
+
public function type(): string
{
return 'standalone-mysql';
@@ -184,12 +193,32 @@ public function portsMappingsArray(): Attribute
);
}
- public function get_db_url(bool $useInternal = false): string
+ protected function internalDbUrl(): Attribute
+ {
+ return new Attribute(
+ get: fn () => "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->uuid}:3306/{$this->mysql_database}",
+ );
+ }
+
+ protected function externalDbUrl(): Attribute
+ {
+ return new Attribute(
+ get: function () {
+ if ($this->is_public && $this->public_port) {
+ return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mysql_database}";
+ }
+
+ return null;
+ }
+ );
+ }
+
+ public function get_db_url(bool $useInternal = false)
{
if ($this->is_public && ! $useInternal) {
- return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mysql_database}";
+ return $this->externalDbUrl;
} else {
- return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->uuid}:3306/{$this->mysql_database}";
+ return $this->internalDbUrl;
}
}
diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php
index 114d376e89..1d5276cf33 100644
--- a/app/Models/StandalonePostgresql.php
+++ b/app/Models/StandalonePostgresql.php
@@ -13,6 +13,8 @@ class StandalonePostgresql extends BaseModel
protected $guarded = [];
+ protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
+
protected $casts = [
'init_scripts' => 'array',
'postgres_password' => 'encrypted',
@@ -179,17 +181,44 @@ public function team()
return data_get($this, 'environment.project.team');
}
+ public function databaseType(): Attribute
+ {
+ return new Attribute(
+ get: fn () => $this->type(),
+ );
+ }
+
public function type(): string
{
return 'standalone-postgresql';
}
- public function get_db_url(bool $useInternal = false): string
+ protected function internalDbUrl(): Attribute
+ {
+ return new Attribute(
+ get: fn () => "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->uuid}:5432/{$this->postgres_db}",
+ );
+ }
+
+ protected function externalDbUrl(): Attribute
+ {
+ return new Attribute(
+ get: function () {
+ if ($this->is_public && $this->public_port) {
+ return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->postgres_db}";
+ }
+
+ return null;
+ }
+ );
+ }
+
+ public function get_db_url(bool $useInternal = false)
{
if ($this->is_public && ! $useInternal) {
- return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->postgres_db}";
+ return $this->externalDbUrl;
} else {
- return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->uuid}:5432/{$this->postgres_db}";
+ return $this->internalDbUrl;
}
}
diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php
index 022cd8d090..e0f863acac 100644
--- a/app/Models/StandaloneRedis.php
+++ b/app/Models/StandaloneRedis.php
@@ -13,6 +13,8 @@ class StandaloneRedis extends BaseModel
protected $guarded = [];
+ protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
+
protected static function booted()
{
static::created(function ($database) {
@@ -179,12 +181,39 @@ public function type(): string
return 'standalone-redis';
}
- public function get_db_url(bool $useInternal = false): string
+ public function databaseType(): Attribute
+ {
+ return new Attribute(
+ get: fn () => $this->type(),
+ );
+ }
+
+ protected function internalDbUrl(): Attribute
+ {
+ return new Attribute(
+ get: fn () => "redis://:{$this->redis_password}@{$this->uuid}:6379/0",
+ );
+ }
+
+ protected function externalDbUrl(): Attribute
+ {
+ return new Attribute(
+ get: function () {
+ if ($this->is_public && $this->public_port) {
+ return "redis://:{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
+ }
+
+ return null;
+ }
+ );
+ }
+
+ public function get_db_url(bool $useInternal = false)
{
if ($this->is_public && ! $useInternal) {
- return "redis://:{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
+ return $this->externalDbUrl;
} else {
- return "redis://:{$this->redis_password}@{$this->uuid}:6379/0";
+ return $this->internalDbUrl;
}
}
diff --git a/bootstrap/helpers/api.php b/bootstrap/helpers/api.php
index c278a5045b..c5083534f9 100644
--- a/bootstrap/helpers/api.php
+++ b/bootstrap/helpers/api.php
@@ -1,24 +1,29 @@
user()->currentAccessToken();
return data_get($token, 'team_id');
}
-function invalid_token()
+function invalidTokenResponse()
{
- return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api-reference/authorization'], 400);
+ return response()->json(['success' => false, 'message' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api-reference/authorization'], 400);
}
-function serialize_api_response($data)
+function serializeApiResponse($data)
{
if (! $data instanceof Collection) {
$data = collect($data);
}
$data = $data->sortKeys();
+
$created_at = data_get($data, 'created_at');
$updated_at = data_get($data, 'updated_at');
if ($created_at) {
@@ -30,9 +35,103 @@ function serialize_api_response($data)
unset($data['updated_at']);
$data['updated_at'] = $updated_at;
}
+ if (data_get($data, 'name')) {
+ $data = $data->prepend($data['name'], 'name');
+ }
+ if (data_get($data, 'description')) {
+ $data = $data->prepend($data['description'], 'description');
+ }
+ if (data_get($data, 'uuid')) {
+ $data = $data->prepend($data['uuid'], 'uuid');
+ }
+
if (data_get($data, 'id')) {
$data = $data->prepend($data['id'], 'id');
}
return $data;
}
+
+function sharedDataApplications()
+{
+ return [
+ 'git_repository' => 'string',
+ 'git_branch' => 'string',
+ 'build_pack' => Rule::enum(BuildPackTypes::class),
+ 'is_static' => 'boolean',
+ 'domains' => 'string',
+ 'redirect' => Rule::enum(RedirectTypes::class),
+ 'git_commit_sha' => 'string',
+ 'docker_registry_image_name' => 'string|nullable',
+ 'docker_registry_image_tag' => 'string|nullable',
+ 'install_command' => 'string|nullable',
+ 'build_command' => 'string|nullable',
+ 'start_command' => 'string|nullable',
+ 'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/',
+ 'ports_mappings' => 'string|regex:/^(\d+:\d+)(,\d+:\d+)*$/|nullable',
+ 'base_directory' => 'string|nullable',
+ 'publish_directory' => 'string|nullable',
+ 'health_check_enabled' => 'boolean',
+ 'health_check_path' => 'string',
+ 'health_check_port' => 'string|nullable',
+ 'health_check_host' => 'string',
+ 'health_check_method' => 'string',
+ 'health_check_return_code' => 'numeric',
+ 'health_check_scheme' => 'string',
+ 'health_check_response_text' => 'string|nullable',
+ 'health_check_interval' => 'numeric',
+ 'health_check_timeout' => 'numeric',
+ 'health_check_retries' => 'numeric',
+ 'health_check_start_period' => 'numeric',
+ 'limits_memory' => 'string',
+ 'limits_memory_swap' => 'string',
+ 'limits_memory_swappiness' => 'numeric',
+ 'limits_memory_reservation' => 'string',
+ 'limits_cpus' => 'string',
+ 'limits_cpuset' => 'string|nullable',
+ 'limits_cpu_shares' => 'numeric',
+ 'custom_labels' => 'string|nullable',
+ 'custom_docker_run_options' => 'string|nullable',
+ 'post_deployment_command' => 'string|nullable',
+ 'post_deployment_command_container' => 'string',
+ 'pre_deployment_command' => 'string|nullable',
+ 'pre_deployment_command_container' => 'string',
+ 'manual_webhook_secret_github' => 'string|nullable',
+ 'manual_webhook_secret_gitlab' => 'string|nullable',
+ 'manual_webhook_secret_bitbucket' => 'string|nullable',
+ 'manual_webhook_secret_gitea' => 'string|nullable',
+ ];
+}
+
+function validateIncomingRequest(Request $request)
+{
+ // check if request is json
+ if (! $request->isJson()) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Invalid request.',
+ 'error' => 'Content-Type must be application/json.',
+ ], 400);
+ }
+ // check if request is valid json
+ if (! json_decode($request->getContent())) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'Invalid request.',
+ 'error' => 'Invalid JSON.',
+ ], 400);
+ }
+}
+
+function removeUnnecessaryFieldsFromRequest(Request $request)
+{
+ $request->offsetUnset('project_uuid');
+ $request->offsetUnset('environment_name');
+ $request->offsetUnset('destination_uuid');
+ $request->offsetUnset('server_uuid');
+ $request->offsetUnset('type');
+ $request->offsetUnset('domains');
+ $request->offsetUnset('instant_deploy');
+ $request->offsetUnset('github_app_uuid');
+ $request->offsetUnset('private_key_uuid');
+}
diff --git a/bootstrap/helpers/databases.php b/bootstrap/helpers/databases.php
index dba8aa543e..ef3f8ac9b4 100644
--- a/bootstrap/helpers/databases.php
+++ b/bootstrap/helpers/databases.php
@@ -19,131 +19,163 @@ function generate_database_name(string $type): string
return $type.'-database-'.$cuid;
}
-function create_standalone_postgresql($environment_id, $destination_uuid): StandalonePostgresql
+function create_standalone_postgresql($environmentId, $destinationUuid, ?array $otherData = null): StandalonePostgresql
{
- // TODO: If another type of destination is added, this will need to be updated.
- $destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
+ $destination = StandaloneDocker::where('uuid', $destinationUuid)->first();
if (! $destination) {
throw new Exception('Destination not found');
}
+ $database = new StandalonePostgresql();
+ $database->name = generate_database_name('postgresql');
+ $database->postgres_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
+ $database->environment_id = $environmentId;
+ $database->destination_id = $destination->id;
+ $database->destination_type = $destination->getMorphClass();
+ if ($otherData) {
+ $database->fill($otherData);
+ }
+ $database->save();
- return StandalonePostgresql::create([
- 'name' => generate_database_name('postgresql'),
- 'postgres_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
- 'environment_id' => $environment_id,
- 'destination_id' => $destination->id,
- 'destination_type' => $destination->getMorphClass(),
- ]);
+ return $database;
}
-function create_standalone_redis($environment_id, $destination_uuid): StandaloneRedis
+function create_standalone_redis($environment_id, $destination_uuid, ?array $otherData = null): StandaloneRedis
{
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (! $destination) {
throw new Exception('Destination not found');
}
+ $database = new StandaloneRedis();
+ $database->name = generate_database_name('redis');
+ $database->redis_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
+ $database->environment_id = $environment_id;
+ $database->destination_id = $destination->id;
+ $database->destination_type = $destination->getMorphClass();
+ if ($otherData) {
+ $database->fill($otherData);
+ }
+ $database->save();
- return StandaloneRedis::create([
- 'name' => generate_database_name('redis'),
- 'redis_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
- 'environment_id' => $environment_id,
- 'destination_id' => $destination->id,
- 'destination_type' => $destination->getMorphClass(),
- ]);
+ return $database;
}
-function create_standalone_mongodb($environment_id, $destination_uuid): StandaloneMongodb
+function create_standalone_mongodb($environment_id, $destination_uuid, ?array $otherData = null): StandaloneMongodb
{
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (! $destination) {
throw new Exception('Destination not found');
}
+ $database = new StandaloneMongodb();
+ $database->name = generate_database_name('mongodb');
+ $database->mongo_initdb_root_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
+ $database->environment_id = $environment_id;
+ $database->destination_id = $destination->id;
+ $database->destination_type = $destination->getMorphClass();
+ if ($otherData) {
+ $database->fill($otherData);
+ }
+ $database->save();
- return StandaloneMongodb::create([
- 'name' => generate_database_name('mongodb'),
- 'mongo_initdb_root_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
- 'environment_id' => $environment_id,
- 'destination_id' => $destination->id,
- 'destination_type' => $destination->getMorphClass(),
- ]);
+ return $database;
}
-function create_standalone_mysql($environment_id, $destination_uuid): StandaloneMysql
+function create_standalone_mysql($environment_id, $destination_uuid, ?array $otherData = null): StandaloneMysql
{
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (! $destination) {
throw new Exception('Destination not found');
}
+ $database = new StandaloneMysql();
+ $database->name = generate_database_name('mysql');
+ $database->mysql_root_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
+ $database->mysql_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
+ $database->environment_id = $environment_id;
+ $database->destination_id = $destination->id;
+ $database->destination_type = $destination->getMorphClass();
+ if ($otherData) {
+ $database->fill($otherData);
+ }
+ $database->save();
- return StandaloneMysql::create([
- 'name' => generate_database_name('mysql'),
- 'mysql_root_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
- 'mysql_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
- 'environment_id' => $environment_id,
- 'destination_id' => $destination->id,
- 'destination_type' => $destination->getMorphClass(),
- ]);
+ return $database;
}
-function create_standalone_mariadb($environment_id, $destination_uuid): StandaloneMariadb
+function create_standalone_mariadb($environment_id, $destination_uuid, ?array $otherData = null): StandaloneMariadb
{
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (! $destination) {
throw new Exception('Destination not found');
}
+ $database = new StandaloneMariadb();
+ $database->name = generate_database_name('mariadb');
+ $database->mariadb_root_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
+ $database->mariadb_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
+ $database->environment_id = $environment_id;
+ $database->destination_id = $destination->id;
+ $database->destination_type = $destination->getMorphClass();
+
+ if ($otherData) {
+ $database->fill($otherData);
+ }
+ $database->save();
- return StandaloneMariadb::create([
- 'name' => generate_database_name('mariadb'),
- 'mariadb_root_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
- 'mariadb_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
- 'environment_id' => $environment_id,
- 'destination_id' => $destination->id,
- 'destination_type' => $destination->getMorphClass(),
- ]);
+ return $database;
}
-function create_standalone_keydb($environment_id, $destination_uuid): StandaloneKeydb
+function create_standalone_keydb($environment_id, $destination_uuid, ?array $otherData = null): StandaloneKeydb
{
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (! $destination) {
throw new Exception('Destination not found');
}
+ $database = new StandaloneKeydb();
+ $database->name = generate_database_name('keydb');
+ $database->keydb_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
+ $database->environment_id = $environment_id;
+ $database->destination_id = $destination->id;
+ $database->destination_type = $destination->getMorphClass();
+ if ($otherData) {
+ $database->fill($otherData);
+ }
+ $database->save();
- return StandaloneKeydb::create([
- 'name' => generate_database_name('keydb'),
- 'keydb_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
- 'environment_id' => $environment_id,
- 'destination_id' => $destination->id,
- 'destination_type' => $destination->getMorphClass(),
- ]);
+ return $database;
}
-function create_standalone_dragonfly($environment_id, $destination_uuid): StandaloneDragonfly
+function create_standalone_dragonfly($environment_id, $destination_uuid, ?array $otherData = null): StandaloneDragonfly
{
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (! $destination) {
throw new Exception('Destination not found');
}
+ $database = new StandaloneDragonfly();
+ $database->name = generate_database_name('dragonfly');
+ $database->dragonfly_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
+ $database->environment_id = $environment_id;
+ $database->destination_id = $destination->id;
+ $database->destination_type = $destination->getMorphClass();
+ if ($otherData) {
+ $database->fill($otherData);
+ }
+ $database->save();
- return StandaloneDragonfly::create([
- 'name' => generate_database_name('dragonfly'),
- 'dragonfly_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
- 'environment_id' => $environment_id,
- 'destination_id' => $destination->id,
- 'destination_type' => $destination->getMorphClass(),
- ]);
+ return $database;
}
-function create_standalone_clickhouse($environment_id, $destination_uuid): StandaloneClickhouse
+function create_standalone_clickhouse($environment_id, $destination_uuid, ?array $otherData = null): StandaloneClickhouse
{
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (! $destination) {
throw new Exception('Destination not found');
}
+ $database = new StandaloneClickhouse();
+ $database->name = generate_database_name('clickhouse');
+ $database->clickhouse_admin_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
+ $database->environment_id = $environment_id;
+ $database->destination_id = $destination->id;
+ $database->destination_type = $destination->getMorphClass();
+ if ($otherData) {
+ $database->fill($otherData);
+ }
+ $database->save();
- return StandaloneClickhouse::create([
- 'name' => generate_database_name('clickhouse'),
- 'clickhouse_admin_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
- 'environment_id' => $environment_id,
- 'destination_id' => $destination->id,
- 'destination_type' => $destination->getMorphClass(),
- ]);
+ return $database;
}
/**
diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php
index 8b4db0695a..09ee5459ea 100644
--- a/bootstrap/helpers/shared.php
+++ b/bootstrap/helpers/shared.php
@@ -56,6 +56,8 @@
use Symfony\Component\Yaml\Yaml;
use Visus\Cuid2\Cuid2;
+use function PHPUnit\Framework\isEmpty;
+
function base_configuration_dir(): string
{
return config('coolify.coolify_root_path') ?? '/data/coolify';
@@ -536,6 +538,43 @@ function getResourceByUuid(string $uuid, ?int $teamId = null)
return null;
}
+function queryDatabaseByUuidWithinTeam(string $uuid, string $teamId)
+{
+ $postgresql = StandalonePostgresql::whereUuid($uuid)->first();
+ if ($postgresql && $postgresql->team()->id == $teamId) {
+ return $postgresql->unsetRelation('environment')->unsetRelation('destination');
+ }
+ $redis = StandaloneRedis::whereUuid($uuid)->first();
+ if ($redis && $redis->team()->id == $teamId) {
+ return $redis->unsetRelation('environment');
+ }
+ $mongodb = StandaloneMongodb::whereUuid($uuid)->first();
+ if ($mongodb && $mongodb->team()->id == $teamId) {
+ return $mongodb->unsetRelation('environment');
+ }
+ $mysql = StandaloneMysql::whereUuid($uuid)->first();
+ if ($mysql && $mysql->team()->id == $teamId) {
+ return $mysql->unsetRelation('environment');
+ }
+ $mariadb = StandaloneMariadb::whereUuid($uuid)->first();
+ if ($mariadb && $mariadb->team()->id == $teamId) {
+ return $mariadb->unsetRelation('environment');
+ }
+ $keydb = StandaloneKeydb::whereUuid($uuid)->first();
+ if ($keydb && $keydb->team()->id == $teamId) {
+ return $keydb->unsetRelation('environment');
+ }
+ $dragonfly = StandaloneDragonfly::whereUuid($uuid)->first();
+ if ($dragonfly && $dragonfly->team()->id == $teamId) {
+ return $dragonfly->unsetRelation('environment');
+ }
+ $clickhouse = StandaloneClickhouse::whereUuid($uuid)->first();
+ if ($clickhouse && $clickhouse->team()->id == $teamId) {
+ return $clickhouse->unsetRelation('environment');
+ }
+
+ return null;
+}
function queryResourcesByUuid(string $uuid)
{
$resource = null;
@@ -2129,6 +2168,75 @@ function ip_match($ip, $cidrs, &$match = null)
return false;
}
+function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId = null)
+{
+ if (is_null($teamId)) {
+ return response()->json(['error' => 'Team ID is required.'], 400);
+ }
+ if (is_array($domains)) {
+ $domains = collect($domains);
+ }
+
+ $domains = $domains->map(function ($domain) {
+ if (str($domain)->endsWith('/')) {
+ $domain = str($domain)->beforeLast('/');
+ }
+
+ return str($domain);
+ });
+ $applications = Application::ownedByCurrentTeamAPI($teamId)->get('fqdn');
+ $serviceApplications = ServiceApplication::ownedByCurrentTeamAPI($teamId)->get('fqdn');
+ $domainFound = false;
+ foreach ($applications as $app) {
+ if (is_null($app->fqdn)) {
+ continue;
+ }
+ $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
+ foreach ($list_of_domains as $domain) {
+ if (str($domain)->endsWith('/')) {
+ $domain = str($domain)->beforeLast('/');
+ }
+ $naked_domain = str($domain)->value();
+ if ($domains->contains($naked_domain)) {
+ $domainFound = true;
+ break;
+ }
+ }
+ }
+ if ($domainFound) {
+ return true;
+ }
+ foreach ($serviceApplications as $app) {
+ if (isEmpty($app->fqdn)) {
+ continue;
+ }
+ $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
+ foreach ($list_of_domains as $domain) {
+ if (str($domain)->endsWith('/')) {
+ $domain = str($domain)->beforeLast('/');
+ }
+ $naked_domain = str($domain)->value();
+ if ($domains->contains($naked_domain)) {
+ $domainFound = true;
+ break;
+ }
+ }
+ }
+ if ($domainFound) {
+ return true;
+ }
+ $settings = InstanceSettings::get();
+ if (data_get($settings, 'fqdn')) {
+ $domain = data_get($settings, 'fqdn');
+ if (str($domain)->endsWith('/')) {
+ $domain = str($domain)->beforeLast('/');
+ }
+ $naked_domain = str($domain)->value();
+ if ($domains->contains($naked_domain)) {
+ return true;
+ }
+ }
+}
function check_domain_usage(ServiceApplication|Application|null $resource = null, ?string $domain = null)
{
if ($resource) {
diff --git a/database/migrations/2024_07_01_115528_add_is_api_allowed_and_iplist.php b/database/migrations/2024_07_01_115528_add_is_api_allowed_and_iplist.php
new file mode 100644
index 0000000000..b319adb70e
--- /dev/null
+++ b/database/migrations/2024_07_01_115528_add_is_api_allowed_and_iplist.php
@@ -0,0 +1,24 @@
+boolean('is_api_enabled')->default(true);
+ $table->text('allowed_ips')->nullable();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::table('instance_settings', function (Blueprint $table) {
+ $table->dropColumn('is_api_enabled');
+ $table->dropColumn('allowed_ips');
+ });
+ }
+};
diff --git a/resources/views/livewire/settings/backup.blade.php b/resources/views/livewire/settings/backup.blade.php
index 50f5f3d286..d517b9516f 100644
--- a/resources/views/livewire/settings/backup.blade.php
+++ b/resources/views/livewire/settings/backup.blade.php
@@ -24,7 +24,7 @@