diff --git a/app/Actions/Application/LoadComposeFile.php b/app/Actions/Application/LoadComposeFile.php
new file mode 100644
index 000000000..838b541e2
--- /dev/null
+++ b/app/Actions/Application/LoadComposeFile.php
@@ -0,0 +1,16 @@
+loadComposeFile();
+ }
+}
diff --git a/app/Actions/Database/RestartDatabase.php b/app/Actions/Database/RestartDatabase.php
new file mode 100644
index 000000000..0400d924d
--- /dev/null
+++ b/app/Actions/Database/RestartDatabase.php
@@ -0,0 +1,29 @@
+destination->server;
+ if (! $server->isFunctional()) {
+ return 'Server is not functional';
+ }
+ StopDatabase::run($database);
+
+ return StartDatabase::run($database);
+ }
+}
diff --git a/app/Actions/Database/StartDatabase.php b/app/Actions/Database/StartDatabase.php
new file mode 100644
index 000000000..323c52ff9
--- /dev/null
+++ b/app/Actions/Database/StartDatabase.php
@@ -0,0 +1,57 @@
+destination->server;
+ if (! $server->isFunctional()) {
+ return 'Server is not functional';
+ }
+ switch ($database->getMorphClass()) {
+ case 'App\Models\StandalonePostgresql':
+ $activity = StartPostgresql::run($database);
+ break;
+ case 'App\Models\StandaloneRedis':
+ $activity = StartRedis::run($database);
+ break;
+ case 'App\Models\StandaloneMongodb':
+ $activity = StartMongodb::run($database);
+ break;
+ case 'App\Models\StandaloneMysql':
+ $activity = StartMysql::run($database);
+ break;
+ case 'App\Models\StandaloneMariadb':
+ $activity = StartMariadb::run($database);
+ break;
+ case 'App\Models\StandaloneKeydb':
+ $activity = StartKeydb::run($database);
+ break;
+ case 'App\Models\StandaloneDragonfly':
+ $activity = StartDragonfly::run($database);
+ break;
+ case 'App\Models\StandaloneClickhouse':
+ $activity = StartClickhouse::run($database);
+ break;
+ }
+ if ($database->is_public && $database->public_port) {
+ StartDatabaseProxy::dispatch($database);
+ }
+
+ return $activity;
+ }
+}
diff --git a/app/Actions/Database/StopDatabase.php b/app/Actions/Database/StopDatabase.php
index 66a32e811..e4903ff35 100644
--- a/app/Actions/Database/StopDatabase.php
+++ b/app/Actions/Database/StopDatabase.php
@@ -29,7 +29,5 @@ class StopDatabase
if ($database->is_public) {
StopDatabaseProxy::run($database);
}
- // TODO: make notification for services
- // $database->environment->project->team->notify(new StatusChanged($database));
}
}
diff --git a/app/Actions/Database/StopDatabaseProxy.php b/app/Actions/Database/StopDatabaseProxy.php
index 1b262c898..b2092e2ef 100644
--- a/app/Actions/Database/StopDatabaseProxy.php
+++ b/app/Actions/Database/StopDatabaseProxy.php
@@ -27,7 +27,6 @@ class StopDatabaseProxy
$server = data_get($database, 'service.server');
}
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);
- $database->is_public = false;
$database->save();
DatabaseStatusChanged::dispatch();
}
diff --git a/app/Actions/Proxy/StartProxy.php b/app/Actions/Proxy/StartProxy.php
index 341186bec..991c94b11 100644
--- a/app/Actions/Proxy/StartProxy.php
+++ b/app/Actions/Proxy/StartProxy.php
@@ -11,11 +11,11 @@ class StartProxy
{
use AsAction;
- public function handle(Server $server, bool $async = true): string|Activity
+ public function handle(Server $server, bool $async = true, bool $force = false): string|Activity
{
try {
$proxyType = $server->proxyType();
- if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) {
+ if ((is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) && $force === false) {
return 'OK';
}
$commands = collect([]);
diff --git a/app/Actions/Service/RestartService.php b/app/Actions/Service/RestartService.php
new file mode 100644
index 000000000..1b6a5c32c
--- /dev/null
+++ b/app/Actions/Service/RestartService.php
@@ -0,0 +1,18 @@
+option('init')) {
+ $this->init();
+
+ return;
+ }
+ if ($this->option('generate-openapi')) {
+ $this->generateOpenApi();
+
+ return;
+ }
+
+ }
+
+ public function generateOpenApi()
+ {
+ // Generate OpenAPI documentation
+ echo "Generating OpenAPI documentation.\n";
+ $process = Process::run(['/var/www/html/vendor/bin/openapi', 'app', '-o', 'openapi.yaml']);
+ $error = $process->errorOutput();
+ $error = preg_replace('/^.*an object literal,.*$/m', '', $error);
+ $error = preg_replace('/^\h*\v+/m', '', $error);
+ echo $error;
+ echo $process->output();
+ }
+
+ public function init()
{
// Generate APP_KEY if not exists
+
if (empty(env('APP_KEY'))) {
echo "Generating APP_KEY.\n";
Artisan::call('key:generate');
diff --git a/app/Enums/BuildPackTypes.php b/app/Enums/BuildPackTypes.php
new file mode 100644
index 000000000..cb51db6d6
--- /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');
+ return false;
}
$this->userId = $userId;
}
- public function broadcastOn(): array
+ public function broadcastOn(): ?array
{
- return [
- new PrivateChannel("user.{$this->userId}"),
- ];
+ if ($this->userId) {
+ return [
+ new PrivateChannel("user.{$this->userId}"),
+ ];
+ }
+
+ return null;
}
}
diff --git a/app/Events/ServiceStatusChanged.php b/app/Events/ServiceStatusChanged.php
index e3e24a248..dc965d0a2 100644
--- a/app/Events/ServiceStatusChanged.php
+++ b/app/Events/ServiceStatusChanged.php
@@ -12,7 +12,7 @@ class ServiceStatusChanged implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
- public $userId;
+ public ?string $userId = null;
public function __construct($userId = null)
{
@@ -20,15 +20,19 @@ class ServiceStatusChanged implements ShouldBroadcast
$userId = auth()->user()->id ?? null;
}
if (is_null($userId)) {
- throw new \Exception('User id is null');
+ return false;
}
$this->userId = $userId;
}
- public function broadcastOn(): array
+ public function broadcastOn(): ?array
{
- return [
- new PrivateChannel("user.{$this->userId}"),
- ];
+ if ($this->userId) {
+ return [
+ new PrivateChannel("user.{$this->userId}"),
+ ];
+ }
+
+ return null;
}
}
diff --git a/app/Http/Controllers/Api/Applications.php b/app/Http/Controllers/Api/Applications.php
deleted file mode 100644
index 82fde140c..000000000
--- 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 000000000..c671ec44d
--- /dev/null
+++ b/app/Http/Controllers/Api/ApplicationsController.php
@@ -0,0 +1,2539 @@
+user()->currentAccessToken();
+ $application->makeHidden([
+ 'id',
+ ]);
+ if ($token->can('view:sensitive')) {
+ return serializeApiResponse($application);
+ }
+ $application->makeHidden([
+ 'custom_labels',
+ 'dockerfile',
+ 'docker_compose',
+ 'docker_compose_raw',
+ 'manual_webhook_secret_bitbucket',
+ 'manual_webhook_secret_gitea',
+ 'manual_webhook_secret_github',
+ 'manual_webhook_secret_gitlab',
+ 'private_key_id',
+ 'value',
+ 'real_value',
+ ]);
+
+ return serializeApiResponse($application);
+ }
+
+ #[OA\Get(
+ summary: 'List',
+ description: 'List all applications.',
+ path: '/applications',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Applications'],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Get all applications.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'array',
+ items: new OA\Items(ref: '#/components/schemas/Application')
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function applications(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $projects = Project::where('team_id', $teamId)->get();
+ $applications = collect();
+ $applications->push($projects->pluck('applications')->flatten());
+ $applications = $applications->flatten();
+ $applications = $applications->map(function ($application) {
+ return $this->removeSensitiveData($application);
+ });
+
+ return response()->json($applications);
+ }
+
+ #[OA\Post(
+ summary: 'Create (Public)',
+ description: 'Create new application based on a public git repository.',
+ path: '/applications/public',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Applications'],
+ requestBody: new OA\RequestBody(
+ description: 'Application object that needs to be created.',
+ required: true,
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ required: ['project_uuid', 'server_uuid', 'environment_name', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'],
+ properties: [
+ 'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
+ 'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
+ 'environment_name' => ['type' => 'string', 'description' => 'The environment name.'],
+ 'git_repository' => ['type' => 'string', 'description' => 'The git repository URL.'],
+ 'git_branch' => ['type' => 'string', 'description' => 'The git branch.'],
+ 'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'],
+ 'ports_exposes' => ['type' => 'string', 'description' => 'The ports to expose.'],
+ 'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID.'],
+ 'name' => ['type' => 'string', 'description' => 'The application name.'],
+ 'description' => ['type' => 'string', 'description' => 'The application description.'],
+ 'domains' => ['type' => 'string', 'description' => 'The application domains.'],
+ 'git_commit_sha' => ['type' => 'string', 'description' => 'The git commit SHA.'],
+ 'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'],
+ 'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'],
+ 'is_static' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is static.'],
+ 'install_command' => ['type' => 'string', 'description' => 'The install command.'],
+ 'build_command' => ['type' => 'string', 'description' => 'The build command.'],
+ 'start_command' => ['type' => 'string', 'description' => 'The start command.'],
+ 'ports_mappings' => ['type' => 'string', 'description' => 'The ports mappings.'],
+ 'base_directory' => ['type' => 'string', 'description' => 'The base directory for all commands.'],
+ 'publish_directory' => ['type' => 'string', 'description' => 'The publish directory.'],
+ 'health_check_enabled' => ['type' => 'boolean', 'description' => 'Health check enabled.'],
+ 'health_check_path' => ['type' => 'string', 'description' => 'Health check path.'],
+ 'health_check_port' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check port.'],
+ 'health_check_host' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check host.'],
+ 'health_check_method' => ['type' => 'string', 'description' => 'Health check method.'],
+ 'health_check_return_code' => ['type' => 'integer', 'description' => 'Health check return code.'],
+ 'health_check_scheme' => ['type' => 'string', 'description' => 'Health check scheme.'],
+ 'health_check_response_text' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check response text.'],
+ 'health_check_interval' => ['type' => 'integer', 'description' => 'Health check interval in seconds.'],
+ 'health_check_timeout' => ['type' => 'integer', 'description' => 'Health check timeout in seconds.'],
+ 'health_check_retries' => ['type' => 'integer', 'description' => 'Health check retries count.'],
+ 'health_check_start_period' => ['type' => 'integer', 'description' => 'Health check start period in seconds.'],
+ 'limits_memory' => ['type' => 'string', 'description' => 'Memory limit.'],
+ 'limits_memory_swap' => ['type' => 'string', 'description' => 'Memory swap limit.'],
+ 'limits_memory_swappiness' => ['type' => 'integer', 'description' => 'Memory swappiness.'],
+ 'limits_memory_reservation' => ['type' => 'string', 'description' => 'Memory reservation.'],
+ 'limits_cpus' => ['type' => 'string', 'description' => 'CPU limit.'],
+ 'limits_cpuset' => ['type' => 'string', 'nullable' => true, 'description' => 'CPU set.'],
+ 'limits_cpu_shares' => ['type' => 'integer', 'description' => 'CPU shares.'],
+ 'custom_labels' => ['type' => 'string', 'description' => 'Custom labels.'],
+ 'custom_docker_run_options' => ['type' => 'string', 'description' => 'Custom docker run options.'],
+ 'post_deployment_command' => ['type' => 'string', 'description' => 'Post deployment command.'],
+ 'post_deployment_command_container' => ['type' => 'string', 'description' => 'Post deployment command container.'],
+ 'pre_deployment_command' => ['type' => 'string', 'description' => 'Pre deployment command.'],
+ 'pre_deployment_command_container' => ['type' => 'string', 'description' => 'Pre deployment command container.'],
+ 'manual_webhook_secret_github' => ['type' => 'string', 'description' => 'Manual webhook secret for Github.'],
+ 'manual_webhook_secret_gitlab' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitlab.'],
+ 'manual_webhook_secret_bitbucket' => ['type' => 'string', 'description' => 'Manual webhook secret for Bitbucket.'],
+ 'manual_webhook_secret_gitea' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitea.'],
+ 'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
+ // 'github_app_uuid' => ['type' => 'string', 'description' => 'The Github App UUID.'],
+ 'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
+ 'dockerfile' => ['type' => 'string', 'description' => 'The Dockerfile content.'],
+ 'docker_compose_location' => ['type' => 'string', 'description' => 'The Docker Compose location.'],
+ 'docker_compose_raw' => ['type' => 'string', 'description' => 'The Docker Compose raw content.'],
+ 'docker_compose_custom_start_command' => ['type' => 'string', 'description' => 'The Docker Compose custom start command.'],
+ 'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
+ 'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
+ 'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
+ ],
+ )),
+ ]),
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Application created successfully.',
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function create_public_application(Request $request)
+ {
+ return $this->create_application($request, 'public');
+ }
+
+ #[OA\Post(
+ summary: 'Create (Private - GH App)',
+ description: 'Create new application based on a private repository through a Github App.',
+ path: '/applications/private-gh-app',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Applications'],
+ requestBody: new OA\RequestBody(
+ description: 'Application object that needs to be created.',
+ required: true,
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ required: ['project_uuid', 'server_uuid', 'environment_name', 'github_app_uuid', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'],
+ properties: [
+ 'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
+ 'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
+ 'environment_name' => ['type' => 'string', 'description' => 'The environment name.'],
+ 'github_app_uuid' => ['type' => 'string', 'description' => 'The Github App UUID.'],
+ 'git_repository' => ['type' => 'string', 'description' => 'The git repository URL.'],
+ 'git_branch' => ['type' => 'string', 'description' => 'The git branch.'],
+ 'ports_exposes' => ['type' => 'string', 'description' => 'The ports to expose.'],
+ 'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID.'],
+ 'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'],
+ 'name' => ['type' => 'string', 'description' => 'The application name.'],
+ 'description' => ['type' => 'string', 'description' => 'The application description.'],
+ 'domains' => ['type' => 'string', 'description' => 'The application domains.'],
+ 'git_commit_sha' => ['type' => 'string', 'description' => 'The git commit SHA.'],
+ 'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'],
+ 'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'],
+ 'is_static' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is static.'],
+ 'install_command' => ['type' => 'string', 'description' => 'The install command.'],
+ 'build_command' => ['type' => 'string', 'description' => 'The build command.'],
+ 'start_command' => ['type' => 'string', 'description' => 'The start command.'],
+ 'ports_mappings' => ['type' => 'string', 'description' => 'The ports mappings.'],
+ 'base_directory' => ['type' => 'string', 'description' => 'The base directory for all commands.'],
+ 'publish_directory' => ['type' => 'string', 'description' => 'The publish directory.'],
+ 'health_check_enabled' => ['type' => 'boolean', 'description' => 'Health check enabled.'],
+ 'health_check_path' => ['type' => 'string', 'description' => 'Health check path.'],
+ 'health_check_port' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check port.'],
+ 'health_check_host' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check host.'],
+ 'health_check_method' => ['type' => 'string', 'description' => 'Health check method.'],
+ 'health_check_return_code' => ['type' => 'integer', 'description' => 'Health check return code.'],
+ 'health_check_scheme' => ['type' => 'string', 'description' => 'Health check scheme.'],
+ 'health_check_response_text' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check response text.'],
+ 'health_check_interval' => ['type' => 'integer', 'description' => 'Health check interval in seconds.'],
+ 'health_check_timeout' => ['type' => 'integer', 'description' => 'Health check timeout in seconds.'],
+ 'health_check_retries' => ['type' => 'integer', 'description' => 'Health check retries count.'],
+ 'health_check_start_period' => ['type' => 'integer', 'description' => 'Health check start period in seconds.'],
+ 'limits_memory' => ['type' => 'string', 'description' => 'Memory limit.'],
+ 'limits_memory_swap' => ['type' => 'string', 'description' => 'Memory swap limit.'],
+ 'limits_memory_swappiness' => ['type' => 'integer', 'description' => 'Memory swappiness.'],
+ 'limits_memory_reservation' => ['type' => 'string', 'description' => 'Memory reservation.'],
+ 'limits_cpus' => ['type' => 'string', 'description' => 'CPU limit.'],
+ 'limits_cpuset' => ['type' => 'string', 'nullable' => true, 'description' => 'CPU set.'],
+ 'limits_cpu_shares' => ['type' => 'integer', 'description' => 'CPU shares.'],
+ 'custom_labels' => ['type' => 'string', 'description' => 'Custom labels.'],
+ 'custom_docker_run_options' => ['type' => 'string', 'description' => 'Custom docker run options.'],
+ 'post_deployment_command' => ['type' => 'string', 'description' => 'Post deployment command.'],
+ 'post_deployment_command_container' => ['type' => 'string', 'description' => 'Post deployment command container.'],
+ 'pre_deployment_command' => ['type' => 'string', 'description' => 'Pre deployment command.'],
+ 'pre_deployment_command_container' => ['type' => 'string', 'description' => 'Pre deployment command container.'],
+ 'manual_webhook_secret_github' => ['type' => 'string', 'description' => 'Manual webhook secret for Github.'],
+ 'manual_webhook_secret_gitlab' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitlab.'],
+ 'manual_webhook_secret_bitbucket' => ['type' => 'string', 'description' => 'Manual webhook secret for Bitbucket.'],
+ 'manual_webhook_secret_gitea' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitea.'],
+ 'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
+ 'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
+ 'dockerfile' => ['type' => 'string', 'description' => 'The Dockerfile content.'],
+ 'docker_compose_location' => ['type' => 'string', 'description' => 'The Docker Compose location.'],
+ 'docker_compose_raw' => ['type' => 'string', 'description' => 'The Docker Compose raw content.'],
+ 'docker_compose_custom_start_command' => ['type' => 'string', 'description' => 'The Docker Compose custom start command.'],
+ 'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
+ 'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
+ 'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
+ ],
+ )),
+ ]),
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Application created successfully.',
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function create_private_gh_app_application(Request $request)
+ {
+ return $this->create_application($request, 'private-gh-app');
+ }
+
+ #[OA\Post(
+ summary: 'Create (Private - Deploy Key)',
+ description: 'Create new application based on a private repository through a Deploy Key.',
+ path: '/applications/private-deploy-key',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Applications'],
+ requestBody: new OA\RequestBody(
+ description: 'Application object that needs to be created.',
+ required: true,
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ required: ['project_uuid', 'server_uuid', 'environment_name', 'private_key_uuid', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'],
+ properties: [
+ 'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
+ 'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
+ 'environment_name' => ['type' => 'string', 'description' => 'The environment name.'],
+ 'private_key_uuid' => ['type' => 'string', 'description' => 'The private key UUID.'],
+ 'git_repository' => ['type' => 'string', 'description' => 'The git repository URL.'],
+ 'git_branch' => ['type' => 'string', 'description' => 'The git branch.'],
+ 'ports_exposes' => ['type' => 'string', 'description' => 'The ports to expose.'],
+ 'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID.'],
+ 'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'],
+ 'name' => ['type' => 'string', 'description' => 'The application name.'],
+ 'description' => ['type' => 'string', 'description' => 'The application description.'],
+ 'domains' => ['type' => 'string', 'description' => 'The application domains.'],
+ 'git_commit_sha' => ['type' => 'string', 'description' => 'The git commit SHA.'],
+ 'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'],
+ 'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'],
+ 'is_static' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is static.'],
+ 'install_command' => ['type' => 'string', 'description' => 'The install command.'],
+ 'build_command' => ['type' => 'string', 'description' => 'The build command.'],
+ 'start_command' => ['type' => 'string', 'description' => 'The start command.'],
+ 'ports_mappings' => ['type' => 'string', 'description' => 'The ports mappings.'],
+ 'base_directory' => ['type' => 'string', 'description' => 'The base directory for all commands.'],
+ 'publish_directory' => ['type' => 'string', 'description' => 'The publish directory.'],
+ 'health_check_enabled' => ['type' => 'boolean', 'description' => 'Health check enabled.'],
+ 'health_check_path' => ['type' => 'string', 'description' => 'Health check path.'],
+ 'health_check_port' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check port.'],
+ 'health_check_host' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check host.'],
+ 'health_check_method' => ['type' => 'string', 'description' => 'Health check method.'],
+ 'health_check_return_code' => ['type' => 'integer', 'description' => 'Health check return code.'],
+ 'health_check_scheme' => ['type' => 'string', 'description' => 'Health check scheme.'],
+ 'health_check_response_text' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check response text.'],
+ 'health_check_interval' => ['type' => 'integer', 'description' => 'Health check interval in seconds.'],
+ 'health_check_timeout' => ['type' => 'integer', 'description' => 'Health check timeout in seconds.'],
+ 'health_check_retries' => ['type' => 'integer', 'description' => 'Health check retries count.'],
+ 'health_check_start_period' => ['type' => 'integer', 'description' => 'Health check start period in seconds.'],
+ 'limits_memory' => ['type' => 'string', 'description' => 'Memory limit.'],
+ 'limits_memory_swap' => ['type' => 'string', 'description' => 'Memory swap limit.'],
+ 'limits_memory_swappiness' => ['type' => 'integer', 'description' => 'Memory swappiness.'],
+ 'limits_memory_reservation' => ['type' => 'string', 'description' => 'Memory reservation.'],
+ 'limits_cpus' => ['type' => 'string', 'description' => 'CPU limit.'],
+ 'limits_cpuset' => ['type' => 'string', 'nullable' => true, 'description' => 'CPU set.'],
+ 'limits_cpu_shares' => ['type' => 'integer', 'description' => 'CPU shares.'],
+ 'custom_labels' => ['type' => 'string', 'description' => 'Custom labels.'],
+ 'custom_docker_run_options' => ['type' => 'string', 'description' => 'Custom docker run options.'],
+ 'post_deployment_command' => ['type' => 'string', 'description' => 'Post deployment command.'],
+ 'post_deployment_command_container' => ['type' => 'string', 'description' => 'Post deployment command container.'],
+ 'pre_deployment_command' => ['type' => 'string', 'description' => 'Pre deployment command.'],
+ 'pre_deployment_command_container' => ['type' => 'string', 'description' => 'Pre deployment command container.'],
+ 'manual_webhook_secret_github' => ['type' => 'string', 'description' => 'Manual webhook secret for Github.'],
+ 'manual_webhook_secret_gitlab' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitlab.'],
+ 'manual_webhook_secret_bitbucket' => ['type' => 'string', 'description' => 'Manual webhook secret for Bitbucket.'],
+ 'manual_webhook_secret_gitea' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitea.'],
+ 'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
+ 'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
+ 'dockerfile' => ['type' => 'string', 'description' => 'The Dockerfile content.'],
+ 'docker_compose_location' => ['type' => 'string', 'description' => 'The Docker Compose location.'],
+ 'docker_compose_raw' => ['type' => 'string', 'description' => 'The Docker Compose raw content.'],
+ 'docker_compose_custom_start_command' => ['type' => 'string', 'description' => 'The Docker Compose custom start command.'],
+ 'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
+ 'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
+ 'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
+ ],
+ )),
+ ]),
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Application created successfully.',
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function create_private_deploy_key_application(Request $request)
+ {
+ return $this->create_application($request, 'private-deploy-key');
+ }
+
+ #[OA\Post(
+ summary: 'Create (Dockerfile)',
+ description: 'Create new application based on a simple Dockerfile.',
+ path: '/applications/dockerfile',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Applications'],
+ requestBody: new OA\RequestBody(
+ description: 'Application object that needs to be created.',
+ required: true,
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ required: ['project_uuid', 'server_uuid', 'environment_name', 'dockerfile'],
+ properties: [
+ 'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
+ 'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
+ 'environment_name' => ['type' => 'string', 'description' => 'The environment name.'],
+ 'dockerfile' => ['type' => 'string', 'description' => 'The Dockerfile content.'],
+ 'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'],
+ 'ports_exposes' => ['type' => 'string', 'description' => 'The ports to expose.'],
+ 'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID.'],
+ 'name' => ['type' => 'string', 'description' => 'The application name.'],
+ 'description' => ['type' => 'string', 'description' => 'The application description.'],
+ 'domains' => ['type' => 'string', 'description' => 'The application domains.'],
+ 'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'],
+ 'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'],
+ 'ports_mappings' => ['type' => 'string', 'description' => 'The ports mappings.'],
+ 'base_directory' => ['type' => 'string', 'description' => 'The base directory for all commands.'],
+ 'health_check_enabled' => ['type' => 'boolean', 'description' => 'Health check enabled.'],
+ 'health_check_path' => ['type' => 'string', 'description' => 'Health check path.'],
+ 'health_check_port' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check port.'],
+ 'health_check_host' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check host.'],
+ 'health_check_method' => ['type' => 'string', 'description' => 'Health check method.'],
+ 'health_check_return_code' => ['type' => 'integer', 'description' => 'Health check return code.'],
+ 'health_check_scheme' => ['type' => 'string', 'description' => 'Health check scheme.'],
+ 'health_check_response_text' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check response text.'],
+ 'health_check_interval' => ['type' => 'integer', 'description' => 'Health check interval in seconds.'],
+ 'health_check_timeout' => ['type' => 'integer', 'description' => 'Health check timeout in seconds.'],
+ 'health_check_retries' => ['type' => 'integer', 'description' => 'Health check retries count.'],
+ 'health_check_start_period' => ['type' => 'integer', 'description' => 'Health check start period in seconds.'],
+ 'limits_memory' => ['type' => 'string', 'description' => 'Memory limit.'],
+ 'limits_memory_swap' => ['type' => 'string', 'description' => 'Memory swap limit.'],
+ 'limits_memory_swappiness' => ['type' => 'integer', 'description' => 'Memory swappiness.'],
+ 'limits_memory_reservation' => ['type' => 'string', 'description' => 'Memory reservation.'],
+ 'limits_cpus' => ['type' => 'string', 'description' => 'CPU limit.'],
+ 'limits_cpuset' => ['type' => 'string', 'nullable' => true, 'description' => 'CPU set.'],
+ 'limits_cpu_shares' => ['type' => 'integer', 'description' => 'CPU shares.'],
+ 'custom_labels' => ['type' => 'string', 'description' => 'Custom labels.'],
+ 'custom_docker_run_options' => ['type' => 'string', 'description' => 'Custom docker run options.'],
+ 'post_deployment_command' => ['type' => 'string', 'description' => 'Post deployment command.'],
+ 'post_deployment_command_container' => ['type' => 'string', 'description' => 'Post deployment command container.'],
+ 'pre_deployment_command' => ['type' => 'string', 'description' => 'Pre deployment command.'],
+ 'pre_deployment_command_container' => ['type' => 'string', 'description' => 'Pre deployment command container.'],
+ 'manual_webhook_secret_github' => ['type' => 'string', 'description' => 'Manual webhook secret for Github.'],
+ 'manual_webhook_secret_gitlab' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitlab.'],
+ 'manual_webhook_secret_bitbucket' => ['type' => 'string', 'description' => 'Manual webhook secret for Bitbucket.'],
+ 'manual_webhook_secret_gitea' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitea.'],
+ 'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
+ 'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
+ ],
+ )),
+ ]),
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Application created successfully.',
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function create_dockerfile_application(Request $request)
+ {
+ return $this->create_application($request, 'dockerfile');
+ }
+
+ #[OA\Post(
+ summary: 'Create (Docker Image)',
+ description: 'Create new application based on a prebuilt docker image',
+ path: '/applications/dockerimage',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Applications'],
+ requestBody: new OA\RequestBody(
+ description: 'Application object that needs to be created.',
+ required: true,
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ required: ['project_uuid', 'server_uuid', 'environment_name', 'docker_registry_image_name', 'ports_exposes'],
+ properties: [
+ 'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
+ 'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
+ 'environment_name' => ['type' => 'string', 'description' => 'The environment name.'],
+ 'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'],
+ 'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'],
+ 'ports_exposes' => ['type' => 'string', 'description' => 'The ports to expose.'],
+ 'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID.'],
+ 'name' => ['type' => 'string', 'description' => 'The application name.'],
+ 'description' => ['type' => 'string', 'description' => 'The application description.'],
+ 'domains' => ['type' => 'string', 'description' => 'The application domains.'],
+ 'ports_mappings' => ['type' => 'string', 'description' => 'The ports mappings.'],
+ 'health_check_enabled' => ['type' => 'boolean', 'description' => 'Health check enabled.'],
+ 'health_check_path' => ['type' => 'string', 'description' => 'Health check path.'],
+ 'health_check_port' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check port.'],
+ 'health_check_host' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check host.'],
+ 'health_check_method' => ['type' => 'string', 'description' => 'Health check method.'],
+ 'health_check_return_code' => ['type' => 'integer', 'description' => 'Health check return code.'],
+ 'health_check_scheme' => ['type' => 'string', 'description' => 'Health check scheme.'],
+ 'health_check_response_text' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check response text.'],
+ 'health_check_interval' => ['type' => 'integer', 'description' => 'Health check interval in seconds.'],
+ 'health_check_timeout' => ['type' => 'integer', 'description' => 'Health check timeout in seconds.'],
+ 'health_check_retries' => ['type' => 'integer', 'description' => 'Health check retries count.'],
+ 'health_check_start_period' => ['type' => 'integer', 'description' => 'Health check start period in seconds.'],
+ 'limits_memory' => ['type' => 'string', 'description' => 'Memory limit.'],
+ 'limits_memory_swap' => ['type' => 'string', 'description' => 'Memory swap limit.'],
+ 'limits_memory_swappiness' => ['type' => 'integer', 'description' => 'Memory swappiness.'],
+ 'limits_memory_reservation' => ['type' => 'string', 'description' => 'Memory reservation.'],
+ 'limits_cpus' => ['type' => 'string', 'description' => 'CPU limit.'],
+ 'limits_cpuset' => ['type' => 'string', 'nullable' => true, 'description' => 'CPU set.'],
+ 'limits_cpu_shares' => ['type' => 'integer', 'description' => 'CPU shares.'],
+ 'custom_labels' => ['type' => 'string', 'description' => 'Custom labels.'],
+ 'custom_docker_run_options' => ['type' => 'string', 'description' => 'Custom docker run options.'],
+ 'post_deployment_command' => ['type' => 'string', 'description' => 'Post deployment command.'],
+ 'post_deployment_command_container' => ['type' => 'string', 'description' => 'Post deployment command container.'],
+ 'pre_deployment_command' => ['type' => 'string', 'description' => 'Pre deployment command.'],
+ 'pre_deployment_command_container' => ['type' => 'string', 'description' => 'Pre deployment command container.'],
+ 'manual_webhook_secret_github' => ['type' => 'string', 'description' => 'Manual webhook secret for Github.'],
+ 'manual_webhook_secret_gitlab' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitlab.'],
+ 'manual_webhook_secret_bitbucket' => ['type' => 'string', 'description' => 'Manual webhook secret for Bitbucket.'],
+ 'manual_webhook_secret_gitea' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitea.'],
+ 'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
+ 'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
+ ],
+ )),
+ ]),
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Application created successfully.',
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function create_dockerimage_application(Request $request)
+ {
+ return $this->create_application($request, 'dockerimage');
+ }
+
+ #[OA\Post(
+ summary: 'Create (Docker Compose)',
+ description: 'Create new application based on a docker-compose file.',
+ path: '/applications/dockercompose',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Applications'],
+ requestBody: new OA\RequestBody(
+ description: 'Application object that needs to be created.',
+ required: true,
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ required: ['project_uuid', 'server_uuid', 'environment_name', 'docker_compose_raw'],
+ properties: [
+ 'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
+ 'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
+ 'environment_name' => ['type' => 'string', 'description' => 'The environment name.'],
+ 'docker_compose_raw' => ['type' => 'string', 'description' => 'The Docker Compose raw content.'],
+ 'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID if the server has more than one destinations.'],
+ 'name' => ['type' => 'string', 'description' => 'The application name.'],
+ 'description' => ['type' => 'string', 'description' => 'The application description.'],
+ 'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
+ ],
+ )),
+ ]),
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Application created successfully.',
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function create_dockercompose_application(Request $request)
+ {
+ return $this->create_application($request, 'dockercompose');
+ }
+
+ private function create_application(Request $request, $type)
+ {
+ $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', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths'];
+ $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',
+ ]);
+
+ $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);
+ }
+
+ $serverUuid = $request->server_uuid;
+ $fqdn = $request->domains;
+ $instantDeploy = $request->instant_deploy;
+ $githubAppUuid = $request->github_app_uuid;
+
+ $project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first();
+ if (! $project) {
+ return response()->json(['message' => 'Project not found.'], 404);
+ }
+ $environment = $project->environments()->where('name', $request->environment_name)->first();
+ if (! $environment) {
+ return response()->json(['message' => 'Environment not found.'], 404);
+ }
+ $server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first();
+ if (! $server) {
+ return response()->json(['message' => 'Server not found.'], 404);
+ }
+ $destinations = $server->destinations();
+ if ($destinations->count() == 0) {
+ return response()->json(['message' => 'Server has no destinations.'], 400);
+ }
+ if ($destinations->count() > 1 && ! $request->has('destination_uuid')) {
+ return response()->json(['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' => ['required', Rule::enum(BuildPackTypes::class)],
+ 'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required',
+ 'docker_compose_location' => 'string',
+ 'docker_compose_raw' => 'string|nullable',
+ 'docker_compose_domains' => 'array|nullable',
+ 'docker_compose_custom_start_command' => 'string|nullable',
+ 'docker_compose_custom_build_command' => 'string|nullable',
+ ]);
+ 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;
+ }
+ $application = new Application();
+ removeUnnecessaryFieldsFromRequest($request);
+
+ $application->fill($request->all());
+ $dockerComposeDomainsJson = collect();
+ if ($request->has('docker_compose_domains')) {
+ $dockerComposeDomains = collect($request->docker_compose_domains);
+ if ($dockerComposeDomains->count() > 0) {
+ $dockerComposeDomains->each(function ($domain, $key) use ($dockerComposeDomainsJson) {
+ $dockerComposeDomainsJson->put(data_get($domain, 'name'), ['domain' => data_get($domain, 'domain')]);
+ });
+ }
+ $request->offsetUnset('docker_compose_domains');
+ }
+ if ($dockerComposeDomainsJson->count() > 0) {
+ $application->docker_compose_domains = $dockerComposeDomainsJson;
+ }
+
+ $application->fqdn = $fqdn;
+ $application->destination_id = $destination->id;
+ $application->destination_type = $destination->getMorphClass();
+ $application->environment_id = $environment->id;
+ $application->save();
+ $application->refresh();
+ $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
+ $application->save();
+ $application->isConfigurationChanged(true);
+
+ if ($instantDeploy) {
+ $deployment_uuid = new Cuid2(7);
+
+ queue_application_deployment(
+ application: $application,
+ deployment_uuid: $deployment_uuid,
+ no_questions_asked: true,
+ is_api: true,
+ );
+ } else {
+ if ($application->build_pack === 'dockercompose') {
+ LoadComposeFile::dispatch($application);
+ }
+ }
+
+ return response()->json(serializeApiResponse([
+ 'uuid' => data_get($application, 'uuid'),
+ 'domains' => data_get($application, 'domains'),
+ ]));
+ } 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',
+ 'watch_paths' => 'string|nullable',
+ 'docker_compose_location' => 'string',
+ 'docker_compose_raw' => 'string|nullable',
+ 'docker_compose_domains' => 'array|nullable',
+ 'docker_compose_custom_start_command' => 'string|nullable',
+ 'docker_compose_custom_build_command' => 'string|nullable',
+ ]);
+ 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;
+ }
+ $githubApp = GithubApp::whereTeamId($teamId)->where('uuid', $githubAppUuid)->first();
+ if (! $githubApp) {
+ return response()->json(['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());
+
+ $dockerComposeDomainsJson = collect();
+ if ($request->has('docker_compose_domains')) {
+ $yaml = Yaml::parse($application->docker_compose_raw);
+ $services = data_get($yaml, 'services');
+ $dockerComposeDomains = collect($request->docker_compose_domains);
+ if ($dockerComposeDomains->count() > 0) {
+ $dockerComposeDomains->each(function ($domain, $key) use ($services, $dockerComposeDomainsJson) {
+ $name = data_get($domain, 'name');
+ if (data_get($services, $name)) {
+ $dockerComposeDomainsJson->put($name, ['domain' => data_get($domain, 'domain')]);
+ }
+ });
+ }
+ $request->offsetUnset('docker_compose_domains');
+ }
+ if ($dockerComposeDomainsJson->count() > 0) {
+ $application->docker_compose_domains = $dockerComposeDomainsJson;
+ }
+ $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();
+ $application->refresh();
+ $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
+ $application->save();
+ $application->isConfigurationChanged(true);
+
+ if ($instantDeploy) {
+ $deployment_uuid = new Cuid2(7);
+
+ queue_application_deployment(
+ application: $application,
+ deployment_uuid: $deployment_uuid,
+ no_questions_asked: true,
+ is_api: true,
+ );
+ } else {
+ if ($application->build_pack === 'dockercompose') {
+ LoadComposeFile::dispatch($application);
+ }
+ }
+
+ return response()->json(serializeApiResponse([
+ 'uuid' => data_get($application, 'uuid'),
+ 'domains' => data_get($application, 'domains'),
+ ]));
+ } 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',
+ 'watch_paths' => 'string|nullable',
+ 'docker_compose_location' => 'string',
+ 'docker_compose_raw' => 'string|nullable',
+ 'docker_compose_domains' => 'array|nullable',
+ 'docker_compose_custom_start_command' => 'string|nullable',
+ 'docker_compose_custom_build_command' => 'string|nullable',
+ ]);
+
+ 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(['message' => 'Private Key not found.'], 404);
+ }
+
+ $application = new Application();
+ removeUnnecessaryFieldsFromRequest($request);
+
+ $application->fill($request->all());
+
+ $dockerComposeDomainsJson = collect();
+ if ($request->has('docker_compose_domains')) {
+ $yaml = Yaml::parse($application->docker_compose_raw);
+ $services = data_get($yaml, 'services');
+ $dockerComposeDomains = collect($request->docker_compose_domains);
+ if ($dockerComposeDomains->count() > 0) {
+ $dockerComposeDomains->each(function ($domain, $key) use ($services, $dockerComposeDomainsJson) {
+ $name = data_get($domain, 'name');
+ if (data_get($services, $name)) {
+ $dockerComposeDomainsJson->put($name, ['domain' => data_get($domain, 'domain')]);
+ }
+ });
+ }
+ $request->offsetUnset('docker_compose_domains');
+ }
+ if ($dockerComposeDomainsJson->count() > 0) {
+ $application->docker_compose_domains = $dockerComposeDomainsJson;
+ }
+ $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();
+ $application->refresh();
+ $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
+ $application->save();
+ $application->isConfigurationChanged(true);
+
+ if ($instantDeploy) {
+ $deployment_uuid = new Cuid2(7);
+
+ queue_application_deployment(
+ application: $application,
+ deployment_uuid: $deployment_uuid,
+ no_questions_asked: true,
+ is_api: true,
+ );
+ } else {
+ if ($application->build_pack === 'dockercompose') {
+ LoadComposeFile::dispatch($application);
+ }
+ }
+
+ return response()->json(serializeApiResponse([
+ 'uuid' => data_get($application, 'uuid'),
+ 'domains' => data_get($application, 'domains'),
+ ]));
+ } 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([
+ '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([
+ '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([
+ '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();
+ $application->refresh();
+ $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
+ $application->save();
+ $application->isConfigurationChanged(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(serializeApiResponse([
+ 'uuid' => data_get($application, 'uuid'),
+ 'domains' => data_get($application, 'domains'),
+ ]));
+ } elseif ($type === 'dockerimage') {
+ 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([
+ '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();
+ $application->refresh();
+ $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
+ $application->save();
+ $application->isConfigurationChanged(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(serializeApiResponse([
+ 'uuid' => data_get($application, 'uuid'),
+ 'domains' => data_get($application, 'domains'),
+ ]));
+ } elseif ($type === 'dockercompose') {
+ $allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'instant_deploy', 'docker_compose_raw'];
+
+ $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('name')) {
+ $request->offsetSet('name', 'service'.new Cuid2(7));
+ }
+ $validator = customApiValidator($request->all(), [
+ sharedDataApplications(),
+ 'docker_compose_raw' => '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;
+ }
+ if (! isBase64Encoded($request->docker_compose_raw)) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $dockerComposeRaw = base64_decode($request->docker_compose_raw);
+ if (mb_detect_encoding($dockerComposeRaw, 'ASCII', true) === false) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $dockerCompose = base64_decode($request->docker_compose_raw);
+ $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->fill($request->all());
+
+ $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) {
+ StartService::dispatch($service);
+ }
+
+ return response()->json(serializeApiResponse([
+ 'uuid' => data_get($service, 'uuid'),
+ 'domains' => data_get($service, 'domains'),
+ ]));
+ }
+
+ return response()->json(['message' => 'Invalid type.'], 400);
+
+ }
+
+ #[OA\Get(
+ summary: 'Get',
+ description: 'Get application by UUID.',
+ path: '/applications/{uuid}',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Applications'],
+ parameters: [
+ new OA\Parameter(
+ name: 'uuid',
+ in: 'path',
+ description: 'UUID of the application.',
+ required: true,
+ schema: new OA\Schema(
+ type: 'string',
+ format: 'uuid',
+ )
+ ),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Get application by UUID.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ ref: '#/components/schemas/Application'
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ public function application_by_uuid(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $uuid = $request->route('uuid');
+ if (! $uuid) {
+ return response()->json(['message' => 'UUID is required.'], 400);
+ }
+ $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
+ if (! $application) {
+ return response()->json(['message' => 'Application not found.'], 404);
+ }
+
+ return response()->json($this->removeSensitiveData($application));
+ }
+
+ #[OA\Delete(
+ summary: 'Delete',
+ description: 'Delete application by UUID.',
+ path: '/applications/{uuid}',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Applications'],
+ parameters: [
+ new OA\Parameter(
+ name: 'uuid',
+ in: 'path',
+ description: 'UUID of the application.',
+ required: true,
+ schema: new OA\Schema(
+ type: 'string',
+ format: 'uuid',
+ )
+ ),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Application deleted.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'message' => ['type' => 'string', 'example' => 'Application deleted.'],
+ ]
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ 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([
+ 'message' => 'Invalid request.',
+ ], 400);
+ }
+ $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
+
+ if (! $application) {
+ return response()->json([
+ 'message' => 'Application not found',
+ ], 404);
+ }
+ DeleteResourceJob::dispatch($application, $cleanup);
+
+ return response()->json([
+ 'message' => 'Application deletion request queued.',
+ ]);
+ }
+
+ #[OA\Patch(
+ summary: 'Update',
+ description: 'Update application by UUID.',
+ path: '/applications',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Applications'],
+ requestBody: new OA\RequestBody(
+ description: 'Application updated.',
+ required: true,
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'],
+ 'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'],
+ 'environment_name' => ['type' => 'string', 'description' => 'The environment name.'],
+ 'github_app_uuid' => ['type' => 'string', 'description' => 'The Github App UUID.'],
+ 'git_repository' => ['type' => 'string', 'description' => 'The git repository URL.'],
+ 'git_branch' => ['type' => 'string', 'description' => 'The git branch.'],
+ 'ports_exposes' => ['type' => 'string', 'description' => 'The ports to expose.'],
+ 'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID.'],
+ 'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'],
+ 'name' => ['type' => 'string', 'description' => 'The application name.'],
+ 'description' => ['type' => 'string', 'description' => 'The application description.'],
+ 'domains' => ['type' => 'string', 'description' => 'The application domains.'],
+ 'git_commit_sha' => ['type' => 'string', 'description' => 'The git commit SHA.'],
+ 'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'],
+ 'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'],
+ 'is_static' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application is static.'],
+ 'install_command' => ['type' => 'string', 'description' => 'The install command.'],
+ 'build_command' => ['type' => 'string', 'description' => 'The build command.'],
+ 'start_command' => ['type' => 'string', 'description' => 'The start command.'],
+ 'ports_mappings' => ['type' => 'string', 'description' => 'The ports mappings.'],
+ 'base_directory' => ['type' => 'string', 'description' => 'The base directory for all commands.'],
+ 'publish_directory' => ['type' => 'string', 'description' => 'The publish directory.'],
+ 'health_check_enabled' => ['type' => 'boolean', 'description' => 'Health check enabled.'],
+ 'health_check_path' => ['type' => 'string', 'description' => 'Health check path.'],
+ 'health_check_port' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check port.'],
+ 'health_check_host' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check host.'],
+ 'health_check_method' => ['type' => 'string', 'description' => 'Health check method.'],
+ 'health_check_return_code' => ['type' => 'integer', 'description' => 'Health check return code.'],
+ 'health_check_scheme' => ['type' => 'string', 'description' => 'Health check scheme.'],
+ 'health_check_response_text' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check response text.'],
+ 'health_check_interval' => ['type' => 'integer', 'description' => 'Health check interval in seconds.'],
+ 'health_check_timeout' => ['type' => 'integer', 'description' => 'Health check timeout in seconds.'],
+ 'health_check_retries' => ['type' => 'integer', 'description' => 'Health check retries count.'],
+ 'health_check_start_period' => ['type' => 'integer', 'description' => 'Health check start period in seconds.'],
+ 'limits_memory' => ['type' => 'string', 'description' => 'Memory limit.'],
+ 'limits_memory_swap' => ['type' => 'string', 'description' => 'Memory swap limit.'],
+ 'limits_memory_swappiness' => ['type' => 'integer', 'description' => 'Memory swappiness.'],
+ 'limits_memory_reservation' => ['type' => 'string', 'description' => 'Memory reservation.'],
+ 'limits_cpus' => ['type' => 'string', 'description' => 'CPU limit.'],
+ 'limits_cpuset' => ['type' => 'string', 'nullable' => true, 'description' => 'CPU set.'],
+ 'limits_cpu_shares' => ['type' => 'integer', 'description' => 'CPU shares.'],
+ 'custom_labels' => ['type' => 'string', 'description' => 'Custom labels.'],
+ 'custom_docker_run_options' => ['type' => 'string', 'description' => 'Custom docker run options.'],
+ 'post_deployment_command' => ['type' => 'string', 'description' => 'Post deployment command.'],
+ 'post_deployment_command_container' => ['type' => 'string', 'description' => 'Post deployment command container.'],
+ 'pre_deployment_command' => ['type' => 'string', 'description' => 'Pre deployment command.'],
+ 'pre_deployment_command_container' => ['type' => 'string', 'description' => 'Pre deployment command container.'],
+ 'manual_webhook_secret_github' => ['type' => 'string', 'description' => 'Manual webhook secret for Github.'],
+ 'manual_webhook_secret_gitlab' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitlab.'],
+ 'manual_webhook_secret_bitbucket' => ['type' => 'string', 'description' => 'Manual webhook secret for Bitbucket.'],
+ 'manual_webhook_secret_gitea' => ['type' => 'string', 'description' => 'Manual webhook secret for Gitea.'],
+ 'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
+ 'instant_deploy' => ['type' => 'boolean', 'description' => 'The flag to indicate if the application should be deployed instantly.'],
+ 'dockerfile' => ['type' => 'string', 'description' => 'The Dockerfile content.'],
+ 'docker_compose_location' => ['type' => 'string', 'description' => 'The Docker Compose location.'],
+ 'docker_compose_raw' => ['type' => 'string', 'description' => 'The Docker Compose raw content.'],
+ 'docker_compose_custom_start_command' => ['type' => 'string', 'description' => 'The Docker Compose custom start command.'],
+ 'docker_compose_custom_build_command' => ['type' => 'string', 'description' => 'The Docker Compose custom build command.'],
+ 'docker_compose_domains' => ['type' => 'array', 'description' => 'The Docker Compose domains.'],
+ 'watch_paths' => ['type' => 'string', 'description' => 'The watch paths.'],
+ ],
+ )),
+ ]),
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Application updated.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'uuid' => ['type' => 'string'],
+ ]
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ public function update_by_uuid(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+
+ if ($request->collect()->count() == 0) {
+ return response()->json([
+ '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([
+ '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_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', '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_raw' => 'string|nullable',
+ 'docker_compose_domains' => 'array|nullable',
+ '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([
+ '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([
+ 'message' => 'Validation failed.',
+ 'errors' => $errors,
+ ], 422);
+ }
+ $domains = $request->domains;
+ if ($request->has('domains') && $server->isProxyShouldRun()) {
+ $errors = [];
+ $fqdn = $request->domains;
+ $fqdn = str($fqdn)->replaceEnd(',', '')->trim();
+ $fqdn = str($fqdn)->replaceStart(',', '')->trim();
+ $application->fqdn = $fqdn;
+ $customLabels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n");
+ $application->custom_labels = base64_encode($customLabels);
+ $request->offsetUnset('domains');
+ }
+
+ $dockerComposeDomainsJson = collect();
+ if ($request->has('docker_compose_domains')) {
+ $yaml = Yaml::parse($application->docker_compose_raw);
+ $services = data_get($yaml, 'services');
+ $dockerComposeDomains = collect($request->docker_compose_domains);
+ if ($dockerComposeDomains->count() > 0) {
+ $dockerComposeDomains->each(function ($domain, $key) use ($services, $dockerComposeDomainsJson) {
+ $name = data_get($domain, 'name');
+ if (data_get($services, $name)) {
+ $dockerComposeDomainsJson->put($name, ['domain' => data_get($domain, 'domain')]);
+ }
+ });
+ }
+ $request->offsetUnset('docker_compose_domains');
+ }
+ $data = $request->all();
+ data_set($data, 'fqdn', $domains);
+ if ($dockerComposeDomainsJson->count() > 0) {
+ data_set($data, 'docker_compose_domains', json_encode($dockerComposeDomainsJson));
+ }
+ $application->fill($data);
+ $application->save();
+
+ return response()->json([
+ 'uuid' => $application->uuid,
+ ]);
+ }
+
+ #[OA\Get(
+ summary: 'List Envs',
+ description: 'List all envs by application UUID.',
+ path: '/applications/{uuid}/envs',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Applications'],
+ parameters: [
+ new OA\Parameter(
+ name: 'uuid',
+ in: 'path',
+ description: 'UUID of the application.',
+ required: true,
+ schema: new OA\Schema(
+ type: 'string',
+ format: 'uuid',
+ )
+ ),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'All environment variables by application UUID.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'array',
+ items: new OA\Items(ref: '#/components/schemas/EnvironmentVariable')
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ public function envs(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
+
+ if (! $application) {
+ return response()->json([
+ 'message' => 'Application not found',
+ ], 404);
+ }
+ $envs = $application->environment_variables->sortBy('id')->merge($application->environment_variables_preview->sortBy('id'));
+
+ $envs = $envs->map(function ($env) {
+ $env->makeHidden([
+ 'service_id',
+ 'standalone_clickhouse_id',
+ 'standalone_dragonfly_id',
+ 'standalone_keydb_id',
+ 'standalone_mariadb_id',
+ 'standalone_mongodb_id',
+ 'standalone_mysql_id',
+ 'standalone_postgresql_id',
+ 'standalone_redis_id',
+ ]);
+ $env = $this->removeSensitiveData($env);
+
+ return $env;
+ });
+
+ return response()->json($envs);
+ }
+
+ #[OA\Patch(
+ summary: 'Update Env',
+ description: 'Update env by application UUID.',
+ path: '/applications/{uuid}/envs',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Applications'],
+ parameters: [
+ new OA\Parameter(
+ name: 'uuid',
+ in: 'path',
+ description: 'UUID of the application.',
+ required: true,
+ schema: new OA\Schema(
+ type: 'string',
+ format: 'uuid',
+ )
+ ),
+ ],
+ requestBody: new OA\RequestBody(
+ description: 'Env updated.',
+ required: true,
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ required: ['key', 'value'],
+ properties: [
+ 'key' => ['type' => 'string', 'description' => 'The key of the environment variable.'],
+ 'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'],
+ 'is_preview' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in preview deployments.'],
+ 'is_build_time' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in build time.'],
+ 'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
+ 'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
+ 'is_shown_once' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable\'s value is shown on the UI.'],
+ ],
+ ),
+ ),
+ ],
+ ),
+ responses: [
+ new OA\Response(
+ response: 201,
+ description: 'Environment variable updated.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'message' => ['type' => 'string', 'example' => 'Environment variable updated.'],
+ ]
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ 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([
+ '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',
+ 'is_multiline' => 'boolean',
+ 'is_shown_once' => '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;
+ }
+ if ($env->is_multiline != $request->is_multiline) {
+ $env->is_multiline = $request->is_multiline;
+ }
+ if ($env->is_shown_once != $request->is_shown_once) {
+ $env->is_shown_once = $request->is_shown_once;
+ }
+ $env->save();
+
+ return response()->json($this->removeSensitiveData($env))->setStatusCode(201);
+ } 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;
+ }
+ if ($env->is_multiline != $request->is_multiline) {
+ $env->is_multiline = $request->is_multiline;
+ }
+ if ($env->is_shown_once != $request->is_shown_once) {
+ $env->is_shown_once = $request->is_shown_once;
+ }
+ $env->save();
+
+ return response()->json($this->removeSensitiveData($env))->setStatusCode(201);
+ } else {
+
+ return response()->json([
+ 'message' => 'Environment variable not found.',
+ ], 404);
+
+ }
+ }
+
+ return response()->json([
+ 'message' => 'Something is not okay. Are you okay?',
+ ], 500);
+
+ }
+
+ #[OA\Patch(
+ summary: 'Update Envs (Bulk)',
+ description: 'Update multiple envs by application UUID.',
+ path: '/applications/{uuid}/envs/bulk',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Applications'],
+ parameters: [
+ new OA\Parameter(
+ name: 'uuid',
+ in: 'path',
+ description: 'UUID of the application.',
+ required: true,
+ schema: new OA\Schema(
+ type: 'string',
+ format: 'uuid',
+ )
+ ),
+ ],
+ requestBody: new OA\RequestBody(
+ description: 'Bulk envs updated.',
+ required: true,
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ required: ['data'],
+ properties: [
+ 'data' => [
+ 'type' => 'array',
+ 'items' => new OA\Schema(
+ type: 'object',
+ properties: [
+ 'key' => ['type' => 'string', 'description' => 'The key of the environment variable.'],
+ 'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'],
+ 'is_preview' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in preview deployments.'],
+ 'is_build_time' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in build time.'],
+ 'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
+ 'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
+ 'is_shown_once' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable\'s value is shown on the UI.'],
+ ],
+ ),
+ ],
+ ],
+ ),
+ ),
+ ],
+ ),
+ responses: [
+ new OA\Response(
+ response: 201,
+ description: 'Environment variables updated.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'message' => ['type' => 'string', 'example' => 'Environment variables updated.'],
+ ]
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ 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([
+ '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',
+ 'is_multiline' => 'boolean',
+ 'is_shown_once' => '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;
+ $is_multi_line = $item->get('is_multiline') ?? false;
+ $is_shown_once = $item->get('is_shown_once') ?? 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;
+ }
+ if ($env->is_multiline != $item->get('is_multiline')) {
+ $env->is_multiline = $item->get('is_multiline');
+ }
+ if ($env->is_shown_once != $item->get('is_shown_once')) {
+ $env->is_shown_once = $item->get('is_shown_once');
+ }
+ $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,
+ 'is_multiline' => $is_multi_line,
+ 'is_shown_once' => $is_shown_once,
+ ]);
+ }
+ } 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;
+ }
+ if ($env->is_multiline != $item->get('is_multiline')) {
+ $env->is_multiline = $item->get('is_multiline');
+ }
+ if ($env->is_shown_once != $item->get('is_shown_once')) {
+ $env->is_shown_once = $item->get('is_shown_once');
+ }
+ $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,
+ 'is_multiline' => $is_multi_line,
+ 'is_shown_once' => $is_shown_once,
+ ]);
+ }
+ }
+ }
+
+ return response()->json($this->removeSensitiveData($env))->setStatusCode(201);
+ }
+
+ #[OA\Post(
+ summary: 'Create Env',
+ description: 'Create env by application UUID.',
+ path: '/applications/{uuid}/envs',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Applications'],
+ parameters: [
+ new OA\Parameter(
+ name: 'uuid',
+ in: 'path',
+ description: 'UUID of the application.',
+ required: true,
+ schema: new OA\Schema(
+ type: 'string',
+ format: 'uuid',
+ )
+ ),
+ ],
+ requestBody: new OA\RequestBody(
+ required: true,
+ description: 'Env created.',
+ content: new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'key' => ['type' => 'string', 'description' => 'The key of the environment variable.'],
+ 'value' => ['type' => 'string', 'description' => 'The value of the environment variable.'],
+ 'is_preview' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in preview deployments.'],
+ 'is_build_time' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is used in build time.'],
+ 'is_literal' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is a literal, nothing espaced.'],
+ 'is_multiline' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable is multiline.'],
+ 'is_shown_once' => ['type' => 'boolean', 'description' => 'The flag to indicate if the environment variable\'s value is shown on the UI.'],
+ ],
+ ),
+ ),
+ ),
+ responses: [
+ new OA\Response(
+ response: 201,
+ description: 'Environment variable created.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'uuid' => ['type' => 'string', 'example' => 'nc0k04gk8g0cgsk440g0koko'],
+ ]
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ 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([
+ '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',
+ 'is_multiline' => 'boolean',
+ 'is_shown_once' => '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,
+ 'is_multiline' => $request->is_multiline ?? false,
+ 'is_shown_once' => $request->is_shown_once ?? false,
+ ]);
+
+ return response()->json([
+ 'uuid' => $env->uuid,
+ ])->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,
+ 'is_multiline' => $request->is_multiline ?? false,
+ 'is_shown_once' => $request->is_shown_once ?? false,
+ ]);
+
+ return response()->json([
+ 'uuid' => $env->uuid,
+ ])->setStatusCode(201);
+
+ }
+ }
+
+ return response()->json([
+ 'message' => 'Something went wrong.',
+ ], 500);
+
+ }
+
+ #[OA\Delete(
+ summary: 'Delete Env',
+ description: 'Delete env by UUID.',
+ path: '/applications/{uuid}/envs/{env_uuid}',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Applications'],
+ parameters: [
+ new OA\Parameter(
+ name: 'uuid',
+ in: 'path',
+ description: 'UUID of the application.',
+ required: true,
+ schema: new OA\Schema(
+ type: 'string',
+ format: 'uuid',
+ )
+ ),
+ new OA\Parameter(
+ name: 'env_uuid',
+ in: 'path',
+ description: 'UUID of the environment variable.',
+ required: true,
+ schema: new OA\Schema(
+ type: 'string',
+ format: 'uuid',
+ )
+ ),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Environment variable deleted.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'message' => ['type' => 'string', 'example' => 'Environment variable deleted.'],
+ ]
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ 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([
+ '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([
+ 'message' => 'Environment variable not found.',
+ ], 404);
+ }
+ $found_env->forceDelete();
+
+ return response()->json([
+ 'message' => 'Environment variable deleted.',
+ ]);
+ }
+
+ #[OA\Get(
+ summary: 'Start',
+ description: 'Start application. `Post` request is also accepted.',
+ path: '/applications/{uuid}/start',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Applications'],
+ parameters: [
+ new OA\Parameter(
+ name: 'uuid',
+ in: 'path',
+ description: 'UUID of the application.',
+ required: true,
+ schema: new OA\Schema(
+ type: 'string',
+ format: 'uuid',
+ )
+ ),
+ new OA\Parameter(
+ name: 'force',
+ in: 'query',
+ description: 'Force rebuild.',
+ schema: new OA\Schema(
+ type: 'boolean',
+ default: false,
+ )
+ ),
+ new OA\Parameter(
+ name: 'instant_deploy',
+ in: 'query',
+ description: 'Instant deploy (skip queuing).',
+ schema: new OA\Schema(
+ type: 'boolean',
+ default: false,
+ )
+ ),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Start application.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'message' => ['type' => 'string', 'example' => 'Deployment request queued.', 'description' => 'Message.'],
+ 'deployment_uuid' => ['type' => 'string', 'example' => 'doogksw', 'description' => 'UUID of the deployment.'],
+ ])
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ 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(['message' => 'UUID is required.'], 400);
+ }
+ $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
+ if (! $application) {
+ return response()->json(['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(
+ [
+ 'message' => 'Deployment request queued.',
+ 'deployment_uuid' => $deployment_uuid->toString(),
+ ],
+ 200
+ );
+ }
+
+ #[OA\Get(
+ summary: 'Stop',
+ description: 'Stop application. `Post` request is also accepted.',
+ path: '/applications/{uuid}/stop',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Applications'],
+ parameters: [
+ new OA\Parameter(
+ name: 'uuid',
+ in: 'path',
+ description: 'UUID of the application.',
+ required: true,
+ schema: new OA\Schema(
+ type: 'string',
+ format: 'uuid',
+ )
+ ),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Stop application.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'message' => ['type' => 'string', 'example' => 'Application stopping request queued.'],
+ ]
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ public function action_stop(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $uuid = $request->route('uuid');
+ if (! $uuid) {
+ return response()->json(['message' => 'UUID is required.'], 400);
+ }
+ $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
+ if (! $application) {
+ return response()->json(['message' => 'Application not found.'], 404);
+ }
+ StopApplication::dispatch($application);
+
+ return response()->json(
+ [
+ 'message' => 'Application stopping request queued.',
+ ],
+ );
+ }
+
+ #[OA\Get(
+ summary: 'Restart',
+ description: 'Restart application. `Post` request is also accepted.',
+ path: '/applications/{uuid}/restart',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Applications'],
+ parameters: [
+ new OA\Parameter(
+ name: 'uuid',
+ in: 'path',
+ description: 'UUID of the application.',
+ required: true,
+ schema: new OA\Schema(
+ type: 'string',
+ format: 'uuid',
+ )
+ ),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Restart application.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'message' => ['type' => 'string', 'example' => 'Restart request queued.'],
+ 'deployment_uuid' => ['type' => 'string', 'example' => 'doogksw', 'description' => 'UUID of the deployment.'],
+ ]
+ )
+ ),
+ ]),
+
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ public function action_restart(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $uuid = $request->route('uuid');
+ if (! $uuid) {
+ return response()->json(['message' => 'UUID is required.'], 400);
+ }
+ $application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
+ if (! $application) {
+ return response()->json(['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(
+ [
+ 'message' => 'Restart request queued.',
+ 'deployment_uuid' => $deployment_uuid->toString(),
+ ],
+ );
+
+ }
+
+ private function validateDataApplications(Request $request, Server $server)
+ {
+ $teamId = getTeamIdFromToken();
+
+ // 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);
+
+ }
+ }
+ if ($request->has('domains') && $server->isProxyShouldRun()) {
+ $uuid = $request->uuid;
+ $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);
+ }
+ if (checkIfDomainIsAlreadyUsed($fqdn, $teamId, $uuid)) {
+ return response()->json([
+ '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 000000000..ef531568c
--- /dev/null
+++ b/app/Http/Controllers/Api/DatabasesController.php
@@ -0,0 +1,1804 @@
+user()->currentAccessToken();
+ $database->makeHidden([
+ 'id',
+ 'laravel_through_key',
+ ]);
+ if ($token->can('view:sensitive')) {
+ return serializeApiResponse($database);
+ }
+
+ $database->makeHidden([
+ 'internal_db_url',
+ 'external_db_url',
+ 'postgres_password',
+ 'dragonfly_password',
+ 'redis_password',
+ 'mongo_initdb_root_password',
+ 'keydb_password',
+ 'clickhouse_admin_password',
+ ]);
+
+ return serializeApiResponse($database);
+ }
+
+ #[OA\Get(
+ summary: 'List',
+ description: 'List all databases.',
+ path: '/databases',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Databases'],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Get all databases',
+ content: new OA\JsonContent(
+ type: 'string',
+ example: 'Content is very complex. Will be implemented later.',
+ ),
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function databases(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $projects = Project::where('team_id', $teamId)->get();
+ $databases = collect();
+ foreach ($projects as $project) {
+ $databases = $databases->merge($project->databases());
+ }
+ $databases = $databases->map(function ($database) {
+ return $this->removeSensitiveData($database);
+ });
+
+ return response()->json($databases);
+ }
+
+ #[OA\Get(
+ summary: 'Get',
+ description: 'Get database by UUID.',
+ path: '/databases/{uuid}',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Databases'],
+ parameters: [
+ new OA\Parameter(
+ name: 'uuid',
+ in: 'path',
+ description: 'UUID of the database.',
+ required: true,
+ schema: new OA\Schema(
+ type: 'string',
+ format: 'uuid',
+ )
+ ),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Get all databases',
+ content: new OA\JsonContent(
+ type: 'string',
+ example: 'Content is very complex. Will be implemented later.',
+ ),
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ public function database_by_uuid(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ if (! $request->uuid) {
+ return response()->json(['message' => 'UUID is required.'], 404);
+ }
+ $database = queryDatabaseByUuidWithinTeam($request->uuid, $teamId);
+ if (! $database) {
+ return response()->json(['message' => 'Database not found.'], 404);
+ }
+
+ return response()->json($this->removeSensitiveData($database));
+ }
+
+ #[OA\Patch(
+ summary: 'Update',
+ description: 'Update database by UUID.',
+ path: '/databases/{uuid}',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Databases'],
+ parameters: [
+ new OA\Parameter(
+ name: 'uuid',
+ in: 'path',
+ description: 'UUID of the database.',
+ required: true,
+ schema: new OA\Schema(
+ type: 'string',
+ format: 'uuid',
+ )
+ ),
+ ],
+ requestBody: new OA\RequestBody(
+ description: 'Database data',
+ required: true,
+ content: new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'name' => ['type' => 'string', 'description' => 'Name of the database'],
+ 'description' => ['type' => 'string', 'description' => 'Description of the database'],
+ 'image' => ['type' => 'string', 'description' => 'Docker Image of the database'],
+ 'is_public' => ['type' => 'boolean', 'description' => 'Is the database public?'],
+ 'public_port' => ['type' => 'integer', 'description' => 'Public port of the database'],
+ 'limits_memory' => ['type' => 'string', 'description' => 'Memory limit of the database'],
+ 'limits_memory_swap' => ['type' => 'string', 'description' => 'Memory swap limit of the database'],
+ 'limits_memory_swappiness' => ['type' => 'integer', 'description' => 'Memory swappiness of the database'],
+ 'limits_memory_reservation' => ['type' => 'string', 'description' => 'Memory reservation of the database'],
+ 'limits_cpus' => ['type' => 'string', 'description' => 'CPU limit of the database'],
+ 'limits_cpuset' => ['type' => 'string', 'description' => 'CPU set of the database'],
+ 'limits_cpu_shares' => ['type' => 'integer', 'description' => 'CPU shares of the database'],
+ 'postgres_user' => ['type' => 'string', 'description' => 'PostgreSQL user'],
+ 'postgres_password' => ['type' => 'string', 'description' => 'PostgreSQL password'],
+ 'postgres_db' => ['type' => 'string', 'description' => 'PostgreSQL database'],
+ 'postgres_initdb_args' => ['type' => 'string', 'description' => 'PostgreSQL initdb args'],
+ 'postgres_host_auth_method' => ['type' => 'string', 'description' => 'PostgreSQL host auth method'],
+ 'postgres_conf' => ['type' => 'string', 'description' => 'PostgreSQL conf'],
+ 'clickhouse_admin_user' => ['type' => 'string', 'description' => 'Clickhouse admin user'],
+ 'clickhouse_admin_password' => ['type' => 'string', 'description' => 'Clickhouse admin password'],
+ 'dragonfly_password' => ['type' => 'string', 'description' => 'DragonFly password'],
+ 'redis_password' => ['type' => 'string', 'description' => 'Redis password'],
+ 'redis_conf' => ['type' => 'string', 'description' => 'Redis conf'],
+ 'keydb_password' => ['type' => 'string', 'description' => 'KeyDB password'],
+ 'keydb_conf' => ['type' => 'string', 'description' => 'KeyDB conf'],
+ 'mariadb_conf' => ['type' => 'string', 'description' => 'MariaDB conf'],
+ 'mariadb_root_password' => ['type' => 'string', 'description' => 'MariaDB root password'],
+ 'mariadb_user' => ['type' => 'string', 'description' => 'MariaDB user'],
+ 'mariadb_password' => ['type' => 'string', 'description' => 'MariaDB password'],
+ 'mariadb_database' => ['type' => 'string', 'description' => 'MariaDB database'],
+ 'mongo_conf' => ['type' => 'string', 'description' => 'Mongo conf'],
+ 'mongo_initdb_root_username' => ['type' => 'string', 'description' => 'Mongo initdb root username'],
+ 'mongo_initdb_root_password' => ['type' => 'string', 'description' => 'Mongo initdb root password'],
+ 'mongo_initdb_init_database' => ['type' => 'string', 'description' => 'Mongo initdb init database'],
+ 'mysql_root_password' => ['type' => 'string', 'description' => 'MySQL root password'],
+ 'mysql_user' => ['type' => 'string', 'description' => 'MySQL user'],
+ 'mysql_database' => ['type' => 'string', 'description' => 'MySQL database'],
+ 'mysql_conf' => ['type' => 'string', 'description' => 'MySQL conf'],
+ ],
+ ),
+ )
+ ),
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Database updated',
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ public function update_by_uuid(Request $request)
+ {
+ $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database', 'mysql_root_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
+ $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',
+ 'image' => 'string',
+ 'is_public' => 'boolean',
+ 'public_port' => 'numeric|nullable',
+ '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',
+ ]);
+
+ if ($validator->fails()) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => $validator->errors(),
+ ], 422);
+ }
+ $uuid = $request->uuid;
+ removeUnnecessaryFieldsFromRequest($request);
+ $database = queryDatabaseByUuidWithinTeam($uuid, $teamId);
+ if (! $database) {
+ return response()->json(['message' => 'Database not found.'], 404);
+ }
+ if ($request->is_public && $request->public_port) {
+ if (isPublicPortAlreadyUsed($database->destination->server, $request->public_port, $database->id)) {
+ return response()->json(['message' => 'Public port already used by another database.'], 400);
+ }
+ }
+ switch ($database->type()) {
+ case 'standalone-postgresql':
+ $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf'];
+ $validator = customApiValidator($request->all(), [
+ 'postgres_user' => 'string',
+ 'postgres_password' => 'string',
+ 'postgres_db' => 'string',
+ 'postgres_initdb_args' => 'string',
+ 'postgres_host_auth_method' => 'string',
+ 'postgres_conf' => 'string',
+ ]);
+ if ($request->has('postgres_conf')) {
+ if (! isBase64Encoded($request->postgres_conf)) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'postgres_conf' => 'The postgres_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $postgresConf = base64_decode($request->postgres_conf);
+ if (mb_detect_encoding($postgresConf, 'ASCII', true) === false) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'postgres_conf' => 'The postgres_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $request->offsetSet('postgres_conf', $postgresConf);
+ }
+ break;
+ case 'standalone-clickhouse':
+ $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'clickhouse_admin_user', 'clickhouse_admin_password'];
+ $validator = customApiValidator($request->all(), [
+ 'clickhouse_admin_user' => 'string',
+ 'clickhouse_admin_password' => 'string',
+ ]);
+ break;
+ case 'standalone-dragonfly':
+ $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'dragonfly_password'];
+ $validator = customApiValidator($request->all(), [
+ 'dragonfly_password' => 'string',
+ ]);
+ break;
+ case 'standalone-redis':
+ $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'redis_password', 'redis_conf'];
+ $validator = customApiValidator($request->all(), [
+ 'redis_password' => 'string',
+ 'redis_conf' => 'string',
+ ]);
+ if ($request->has('redis_conf')) {
+ if (! isBase64Encoded($request->redis_conf)) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'redis_conf' => 'The redis_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $redisConf = base64_decode($request->redis_conf);
+ if (mb_detect_encoding($redisConf, 'ASCII', true) === false) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'redis_conf' => 'The redis_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $request->offsetSet('redis_conf', $redisConf);
+ }
+ break;
+ case 'standalone-keydb':
+ $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'keydb_password', 'keydb_conf'];
+ $validator = customApiValidator($request->all(), [
+ 'keydb_password' => 'string',
+ 'keydb_conf' => 'string',
+ ]);
+ if ($request->has('keydb_conf')) {
+ if (! isBase64Encoded($request->keydb_conf)) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'keydb_conf' => 'The keydb_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $keydbConf = base64_decode($request->keydb_conf);
+ if (mb_detect_encoding($keydbConf, 'ASCII', true) === false) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'keydb_conf' => 'The keydb_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $request->offsetSet('keydb_conf', $keydbConf);
+ }
+ break;
+ case 'standalone-mariadb':
+ $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database'];
+ $validator = customApiValidator($request->all(), [
+ 'mariadb_conf' => 'string',
+ 'mariadb_root_password' => 'string',
+ 'mariadb_user' => 'string',
+ 'mariadb_password' => 'string',
+ 'mariadb_database' => 'string',
+ ]);
+ if ($request->has('mariadb_conf')) {
+ if (! isBase64Encoded($request->mariadb_conf)) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'mariadb_conf' => 'The mariadb_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $mariadbConf = base64_decode($request->mariadb_conf);
+ if (mb_detect_encoding($mariadbConf, 'ASCII', true) === false) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'mariadb_conf' => 'The mariadb_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $request->offsetSet('mariadb_conf', $mariadbConf);
+ }
+ break;
+ case 'standalone-mongodb':
+ $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database'];
+ $validator = customApiValidator($request->all(), [
+ 'mongo_conf' => 'string',
+ 'mongo_initdb_root_username' => 'string',
+ 'mongo_initdb_root_password' => 'string',
+ 'mongo_initdb_init_database' => 'string',
+ ]);
+ if ($request->has('mongo_conf')) {
+ if (! isBase64Encoded($request->mongo_conf)) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'mongo_conf' => 'The mongo_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $mongoConf = base64_decode($request->mongo_conf);
+ if (mb_detect_encoding($mongoConf, 'ASCII', true) === false) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'mongo_conf' => 'The mongo_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $request->offsetSet('mongo_conf', $mongoConf);
+ }
+
+ break;
+ case 'standalone-mysql':
+ $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
+ $validator = customApiValidator($request->all(), [
+ 'mysql_root_password' => 'string',
+ 'mysql_user' => 'string',
+ 'mysql_database' => 'string',
+ 'mysql_conf' => 'string',
+ ]);
+ if ($request->has('mysql_conf')) {
+ if (! isBase64Encoded($request->mysql_conf)) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'mysql_conf' => 'The mysql_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $mysqlConf = base64_decode($request->mysql_conf);
+ if (mb_detect_encoding($mysqlConf, 'ASCII', true) === false) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'mysql_conf' => 'The mysql_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $request->offsetSet('mysql_conf', $mysqlConf);
+ }
+ break;
+
+ }
+ $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);
+ }
+ $whatToDoWithDatabaseProxy = null;
+ if ($request->is_public === false && $database->is_public === true) {
+ $whatToDoWithDatabaseProxy = 'stop';
+ }
+ if ($request->is_public === true && $request->public_port && $database->is_public === false) {
+ $whatToDoWithDatabaseProxy = 'start';
+ }
+
+ $database->update($request->all());
+
+ if ($whatToDoWithDatabaseProxy === 'start') {
+ StartDatabaseProxy::dispatch($database);
+ } elseif ($whatToDoWithDatabaseProxy === 'stop') {
+ StopDatabaseProxy::dispatch($database);
+ }
+
+ return response()->json([
+ 'message' => 'Database updated.',
+ ]);
+
+ }
+
+ #[OA\Post(
+ summary: 'Create (PostgreSQL)',
+ description: 'Create a new PostgreSQL database.',
+ path: '/databases/postgresql',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Databases'],
+
+ requestBody: new OA\RequestBody(
+ description: 'Database data',
+ required: true,
+ content: new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ required: ['server_uuid', 'project_uuid', 'environment_name'],
+ properties: [
+ 'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
+ 'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
+ 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'],
+ 'postgres_user' => ['type' => 'string', 'description' => 'PostgreSQL user'],
+ 'postgres_password' => ['type' => 'string', 'description' => 'PostgreSQL password'],
+ 'postgres_db' => ['type' => 'string', 'description' => 'PostgreSQL database'],
+ 'postgres_initdb_args' => ['type' => 'string', 'description' => 'PostgreSQL initdb args'],
+ 'postgres_host_auth_method' => ['type' => 'string', 'description' => 'PostgreSQL host auth method'],
+ 'postgres_conf' => ['type' => 'string', 'description' => 'PostgreSQL conf'],
+ 'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
+ 'name' => ['type' => 'string', 'description' => 'Name of the database'],
+ 'description' => ['type' => 'string', 'description' => 'Description of the database'],
+ 'image' => ['type' => 'string', 'description' => 'Docker Image of the database'],
+ 'is_public' => ['type' => 'boolean', 'description' => 'Is the database public?'],
+ 'public_port' => ['type' => 'integer', 'description' => 'Public port of the database'],
+ 'limits_memory' => ['type' => 'string', 'description' => 'Memory limit of the database'],
+ 'limits_memory_swap' => ['type' => 'string', 'description' => 'Memory swap limit of the database'],
+ 'limits_memory_swappiness' => ['type' => 'integer', 'description' => 'Memory swappiness of the database'],
+ 'limits_memory_reservation' => ['type' => 'string', 'description' => 'Memory reservation of the database'],
+ 'limits_cpus' => ['type' => 'string', 'description' => 'CPU limit of the database'],
+ 'limits_cpuset' => ['type' => 'string', 'description' => 'CPU set of the database'],
+ 'limits_cpu_shares' => ['type' => 'integer', 'description' => 'CPU shares of the database'],
+ 'instant_deploy' => ['type' => 'boolean', 'description' => 'Instant deploy the database'],
+ ],
+ ),
+ )
+ ),
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Database updated',
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function create_database_postgresql(Request $request)
+ {
+ return $this->create_database($request, NewDatabaseTypes::POSTGRESQL);
+ }
+
+ #[OA\Post(
+ summary: 'Create (Clickhouse)',
+ description: 'Create a new Clickhouse database.',
+ path: '/databases/clickhouse',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Databases'],
+
+ requestBody: new OA\RequestBody(
+ description: 'Database data',
+ required: true,
+ content: new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ required: ['server_uuid', 'project_uuid', 'environment_name'],
+ properties: [
+ 'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
+ 'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
+ 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'],
+ 'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
+ 'clickhouse_admin_user' => ['type' => 'string', 'description' => 'Clickhouse admin user'],
+ 'clickhouse_admin_password' => ['type' => 'string', 'description' => 'Clickhouse admin password'],
+ 'name' => ['type' => 'string', 'description' => 'Name of the database'],
+ 'description' => ['type' => 'string', 'description' => 'Description of the database'],
+ 'image' => ['type' => 'string', 'description' => 'Docker Image of the database'],
+ 'is_public' => ['type' => 'boolean', 'description' => 'Is the database public?'],
+ 'public_port' => ['type' => 'integer', 'description' => 'Public port of the database'],
+ 'limits_memory' => ['type' => 'string', 'description' => 'Memory limit of the database'],
+ 'limits_memory_swap' => ['type' => 'string', 'description' => 'Memory swap limit of the database'],
+ 'limits_memory_swappiness' => ['type' => 'integer', 'description' => 'Memory swappiness of the database'],
+ 'limits_memory_reservation' => ['type' => 'string', 'description' => 'Memory reservation of the database'],
+ 'limits_cpus' => ['type' => 'string', 'description' => 'CPU limit of the database'],
+ 'limits_cpuset' => ['type' => 'string', 'description' => 'CPU set of the database'],
+ 'limits_cpu_shares' => ['type' => 'integer', 'description' => 'CPU shares of the database'],
+ 'instant_deploy' => ['type' => 'boolean', 'description' => 'Instant deploy the database'],
+ ],
+ ),
+ )
+ ),
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Database updated',
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function create_database_clickhouse(Request $request)
+ {
+ return $this->create_database($request, NewDatabaseTypes::CLICKHOUSE);
+ }
+
+ #[OA\Post(
+ summary: 'Create (DragonFly)',
+ description: 'Create a new DragonFly database.',
+ path: '/databases/dragonfly',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Databases'],
+
+ requestBody: new OA\RequestBody(
+ description: 'Database data',
+ required: true,
+ content: new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ required: ['server_uuid', 'project_uuid', 'environment_name'],
+ properties: [
+ 'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
+ 'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
+ 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'],
+ 'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
+ 'dragonfly_password' => ['type' => 'string', 'description' => 'DragonFly password'],
+ 'name' => ['type' => 'string', 'description' => 'Name of the database'],
+ 'description' => ['type' => 'string', 'description' => 'Description of the database'],
+ 'image' => ['type' => 'string', 'description' => 'Docker Image of the database'],
+ 'is_public' => ['type' => 'boolean', 'description' => 'Is the database public?'],
+ 'public_port' => ['type' => 'integer', 'description' => 'Public port of the database'],
+ 'limits_memory' => ['type' => 'string', 'description' => 'Memory limit of the database'],
+ 'limits_memory_swap' => ['type' => 'string', 'description' => 'Memory swap limit of the database'],
+ 'limits_memory_swappiness' => ['type' => 'integer', 'description' => 'Memory swappiness of the database'],
+ 'limits_memory_reservation' => ['type' => 'string', 'description' => 'Memory reservation of the database'],
+ 'limits_cpus' => ['type' => 'string', 'description' => 'CPU limit of the database'],
+ 'limits_cpuset' => ['type' => 'string', 'description' => 'CPU set of the database'],
+ 'limits_cpu_shares' => ['type' => 'integer', 'description' => 'CPU shares of the database'],
+ 'instant_deploy' => ['type' => 'boolean', 'description' => 'Instant deploy the database'],
+ ],
+ ),
+ )
+ ),
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Database updated',
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function create_database_dragonfly(Request $request)
+ {
+ return $this->create_database($request, NewDatabaseTypes::DRAGONFLY);
+ }
+
+ #[OA\Post(
+ summary: 'Create (Redis)',
+ description: 'Create a new Redis database.',
+ path: '/databases/redis',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Databases'],
+
+ requestBody: new OA\RequestBody(
+ description: 'Database data',
+ required: true,
+ content: new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ required: ['server_uuid', 'project_uuid', 'environment_name'],
+ properties: [
+ 'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
+ 'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
+ 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'],
+ 'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
+ 'redis_password' => ['type' => 'string', 'description' => 'Redis password'],
+ 'redis_conf' => ['type' => 'string', 'description' => 'Redis conf'],
+ 'name' => ['type' => 'string', 'description' => 'Name of the database'],
+ 'description' => ['type' => 'string', 'description' => 'Description of the database'],
+ 'image' => ['type' => 'string', 'description' => 'Docker Image of the database'],
+ 'is_public' => ['type' => 'boolean', 'description' => 'Is the database public?'],
+ 'public_port' => ['type' => 'integer', 'description' => 'Public port of the database'],
+ 'limits_memory' => ['type' => 'string', 'description' => 'Memory limit of the database'],
+ 'limits_memory_swap' => ['type' => 'string', 'description' => 'Memory swap limit of the database'],
+ 'limits_memory_swappiness' => ['type' => 'integer', 'description' => 'Memory swappiness of the database'],
+ 'limits_memory_reservation' => ['type' => 'string', 'description' => 'Memory reservation of the database'],
+ 'limits_cpus' => ['type' => 'string', 'description' => 'CPU limit of the database'],
+ 'limits_cpuset' => ['type' => 'string', 'description' => 'CPU set of the database'],
+ 'limits_cpu_shares' => ['type' => 'integer', 'description' => 'CPU shares of the database'],
+ 'instant_deploy' => ['type' => 'boolean', 'description' => 'Instant deploy the database'],
+ ],
+ ),
+ )
+ ),
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Database updated',
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function create_database_redis(Request $request)
+ {
+ return $this->create_database($request, NewDatabaseTypes::REDIS);
+ }
+
+ #[OA\Post(
+ summary: 'Create (KeyDB)',
+ description: 'Create a new KeyDB database.',
+ path: '/databases/keydb',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Databases'],
+
+ requestBody: new OA\RequestBody(
+ description: 'Database data',
+ required: true,
+ content: new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ required: ['server_uuid', 'project_uuid', 'environment_name'],
+ properties: [
+ 'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
+ 'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
+ 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'],
+ 'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
+ 'keydb_password' => ['type' => 'string', 'description' => 'KeyDB password'],
+ 'keydb_conf' => ['type' => 'string', 'description' => 'KeyDB conf'],
+ 'name' => ['type' => 'string', 'description' => 'Name of the database'],
+ 'description' => ['type' => 'string', 'description' => 'Description of the database'],
+ 'image' => ['type' => 'string', 'description' => 'Docker Image of the database'],
+ 'is_public' => ['type' => 'boolean', 'description' => 'Is the database public?'],
+ 'public_port' => ['type' => 'integer', 'description' => 'Public port of the database'],
+ 'limits_memory' => ['type' => 'string', 'description' => 'Memory limit of the database'],
+ 'limits_memory_swap' => ['type' => 'string', 'description' => 'Memory swap limit of the database'],
+ 'limits_memory_swappiness' => ['type' => 'integer', 'description' => 'Memory swappiness of the database'],
+ 'limits_memory_reservation' => ['type' => 'string', 'description' => 'Memory reservation of the database'],
+ 'limits_cpus' => ['type' => 'string', 'description' => 'CPU limit of the database'],
+ 'limits_cpuset' => ['type' => 'string', 'description' => 'CPU set of the database'],
+ 'limits_cpu_shares' => ['type' => 'integer', 'description' => 'CPU shares of the database'],
+ 'instant_deploy' => ['type' => 'boolean', 'description' => 'Instant deploy the database'],
+ ],
+ ),
+ )
+ ),
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Database updated',
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function create_database_keydb(Request $request)
+ {
+ return $this->create_database($request, NewDatabaseTypes::KEYDB);
+ }
+
+ #[OA\Post(
+ summary: 'Create (MariaDB)',
+ description: 'Create a new MariaDB database.',
+ path: '/databases/mariadb',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Databases'],
+
+ requestBody: new OA\RequestBody(
+ description: 'Database data',
+ required: true,
+ content: new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ required: ['server_uuid', 'project_uuid', 'environment_name'],
+ properties: [
+ 'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
+ 'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
+ 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'],
+ 'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
+ 'mariadb_conf' => ['type' => 'string', 'description' => 'MariaDB conf'],
+ 'mariadb_root_password' => ['type' => 'string', 'description' => 'MariaDB root password'],
+ 'mariadb_user' => ['type' => 'string', 'description' => 'MariaDB user'],
+ 'mariadb_password' => ['type' => 'string', 'description' => 'MariaDB password'],
+ 'mariadb_database' => ['type' => 'string', 'description' => 'MariaDB database'],
+ 'name' => ['type' => 'string', 'description' => 'Name of the database'],
+ 'description' => ['type' => 'string', 'description' => 'Description of the database'],
+ 'image' => ['type' => 'string', 'description' => 'Docker Image of the database'],
+ 'is_public' => ['type' => 'boolean', 'description' => 'Is the database public?'],
+ 'public_port' => ['type' => 'integer', 'description' => 'Public port of the database'],
+ 'limits_memory' => ['type' => 'string', 'description' => 'Memory limit of the database'],
+ 'limits_memory_swap' => ['type' => 'string', 'description' => 'Memory swap limit of the database'],
+ 'limits_memory_swappiness' => ['type' => 'integer', 'description' => 'Memory swappiness of the database'],
+ 'limits_memory_reservation' => ['type' => 'string', 'description' => 'Memory reservation of the database'],
+ 'limits_cpus' => ['type' => 'string', 'description' => 'CPU limit of the database'],
+ 'limits_cpuset' => ['type' => 'string', 'description' => 'CPU set of the database'],
+ 'limits_cpu_shares' => ['type' => 'integer', 'description' => 'CPU shares of the database'],
+ 'instant_deploy' => ['type' => 'boolean', 'description' => 'Instant deploy the database'],
+ ],
+ ),
+ )
+ ),
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Database updated',
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function create_database_mariadb(Request $request)
+ {
+ return $this->create_database($request, NewDatabaseTypes::MARIADB);
+ }
+
+ #[OA\Post(
+ summary: 'Create (MySQL)',
+ description: 'Create a new MySQL database.',
+ path: '/databases/mysql',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Databases'],
+
+ requestBody: new OA\RequestBody(
+ description: 'Database data',
+ required: true,
+ content: new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ required: ['server_uuid', 'project_uuid', 'environment_name'],
+ properties: [
+ 'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
+ 'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
+ 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'],
+ 'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
+ 'mysql_root_password' => ['type' => 'string', 'description' => 'MySQL root password'],
+ 'mysql_user' => ['type' => 'string', 'description' => 'MySQL user'],
+ 'mysql_database' => ['type' => 'string', 'description' => 'MySQL database'],
+ 'mysql_conf' => ['type' => 'string', 'description' => 'MySQL conf'],
+ 'name' => ['type' => 'string', 'description' => 'Name of the database'],
+ 'description' => ['type' => 'string', 'description' => 'Description of the database'],
+ 'image' => ['type' => 'string', 'description' => 'Docker Image of the database'],
+ 'is_public' => ['type' => 'boolean', 'description' => 'Is the database public?'],
+ 'public_port' => ['type' => 'integer', 'description' => 'Public port of the database'],
+ 'limits_memory' => ['type' => 'string', 'description' => 'Memory limit of the database'],
+ 'limits_memory_swap' => ['type' => 'string', 'description' => 'Memory swap limit of the database'],
+ 'limits_memory_swappiness' => ['type' => 'integer', 'description' => 'Memory swappiness of the database'],
+ 'limits_memory_reservation' => ['type' => 'string', 'description' => 'Memory reservation of the database'],
+ 'limits_cpus' => ['type' => 'string', 'description' => 'CPU limit of the database'],
+ 'limits_cpuset' => ['type' => 'string', 'description' => 'CPU set of the database'],
+ 'limits_cpu_shares' => ['type' => 'integer', 'description' => 'CPU shares of the database'],
+ 'instant_deploy' => ['type' => 'boolean', 'description' => 'Instant deploy the database'],
+ ],
+ ),
+ )
+ ),
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Database updated',
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function create_database_mysql(Request $request)
+ {
+ return $this->create_database($request, NewDatabaseTypes::MYSQL);
+ }
+
+ #[OA\Post(
+ summary: 'Create (MongoDB)',
+ description: 'Create a new MongoDB database.',
+ path: '/databases/mongodb',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Databases'],
+
+ requestBody: new OA\RequestBody(
+ description: 'Database data',
+ required: true,
+ content: new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ required: ['server_uuid', 'project_uuid', 'environment_name'],
+ properties: [
+ 'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'],
+ 'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'],
+ 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'],
+ 'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'],
+ 'mongo_conf' => ['type' => 'string', 'description' => 'MongoDB conf'],
+ 'mongo_initdb_root_username' => ['type' => 'string', 'description' => 'MongoDB initdb root username'],
+ 'name' => ['type' => 'string', 'description' => 'Name of the database'],
+ 'description' => ['type' => 'string', 'description' => 'Description of the database'],
+ 'image' => ['type' => 'string', 'description' => 'Docker Image of the database'],
+ 'is_public' => ['type' => 'boolean', 'description' => 'Is the database public?'],
+ 'public_port' => ['type' => 'integer', 'description' => 'Public port of the database'],
+ 'limits_memory' => ['type' => 'string', 'description' => 'Memory limit of the database'],
+ 'limits_memory_swap' => ['type' => 'string', 'description' => 'Memory swap limit of the database'],
+ 'limits_memory_swappiness' => ['type' => 'integer', 'description' => 'Memory swappiness of the database'],
+ 'limits_memory_reservation' => ['type' => 'string', 'description' => 'Memory reservation of the database'],
+ 'limits_cpus' => ['type' => 'string', 'description' => 'CPU limit of the database'],
+ 'limits_cpuset' => ['type' => 'string', 'description' => 'CPU set of the database'],
+ 'limits_cpu_shares' => ['type' => 'integer', 'description' => 'CPU shares of the database'],
+ 'instant_deploy' => ['type' => 'boolean', 'description' => 'Instant deploy the database'],
+ ],
+ ),
+ )
+ ),
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Database updated',
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function create_database_mongodb(Request $request)
+ {
+ return $this->create_database($request, NewDatabaseTypes::MONGODB);
+ }
+
+ public function create_database(Request $request, NewDatabaseTypes $type)
+ {
+ $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database', 'mysql_root_password', 'mysql_user', 'mysql_database', 'mysql_conf'];
+
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+
+ $return = validateIncomingRequest($request);
+ if ($return instanceof \Illuminate\Http\JsonResponse) {
+ return $return;
+ }
+
+ $extraFields = array_diff(array_keys($request->all()), $allowedFields);
+ if (! empty($extraFields)) {
+ $errors = collect([]);
+ if (! empty($extraFields)) {
+ foreach ($extraFields as $field) {
+ $errors->add($field, 'This field is not allowed.');
+ }
+ }
+
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => $errors,
+ ], 422);
+ }
+ $serverUuid = $request->server_uuid;
+ $instantDeploy = $request->instant_deploy ?? false;
+ if ($request->is_public && ! $request->public_port) {
+ $request->offsetSet('is_public', false);
+ }
+ $project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first();
+ if (! $project) {
+ return response()->json(['message' => 'Project not found.'], 404);
+ }
+ $environment = $project->environments()->where('name', $request->environment_name)->first();
+ if (! $environment) {
+ return response()->json(['message' => 'Environment not found.'], 404);
+ }
+ $server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first();
+ if (! $server) {
+ return response()->json(['message' => 'Server not found.'], 404);
+ }
+ $destinations = $server->destinations();
+ if ($destinations->count() == 0) {
+ return response()->json(['message' => 'Server has no destinations.'], 400);
+ }
+ if ($destinations->count() > 1 && ! $request->has('destination_uuid')) {
+ return response()->json(['message' => 'Server has multiple destinations and you do not set destination_uuid.'], 400);
+ }
+ $destination = $destinations->first();
+ if ($request->has('public_port') && $request->is_public) {
+ if (isPublicPortAlreadyUsed($server, $request->public_port)) {
+ return response()->json(['message' => 'Public port already used by another database.'], 400);
+ }
+ }
+ $validator = customApiValidator($request->all(), [
+ 'name' => 'string|max:255',
+ 'description' => 'string|nullable',
+ 'image' => 'string',
+ 'project_uuid' => 'string|required',
+ 'environment_name' => 'string|required',
+ 'server_uuid' => 'string|required',
+ 'destination_uuid' => 'string',
+ 'is_public' => 'boolean',
+ 'public_port' => 'numeric|nullable',
+ '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',
+ ]);
+ if ($validator->failed()) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => $validator->errors(),
+ ], 422);
+ }
+ if ($request->public_port) {
+ if ($request->public_port < 1024 || $request->public_port > 65535) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'public_port' => 'The public port should be between 1024 and 65535.',
+ ],
+ ], 422);
+ }
+ }
+ if ($type === NewDatabaseTypes::POSTGRESQL) {
+ $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf'];
+ $validator = customApiValidator($request->all(), [
+ 'postgres_user' => 'string',
+ 'postgres_password' => 'string',
+ 'postgres_db' => 'string',
+ 'postgres_initdb_args' => 'string',
+ 'postgres_host_auth_method' => 'string',
+ 'postgres_conf' => '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([
+ 'message' => 'Validation failed.',
+ 'errors' => $errors,
+ ], 422);
+ }
+ removeUnnecessaryFieldsFromRequest($request);
+ if ($request->has('postgres_conf')) {
+ if (! isBase64Encoded($request->postgres_conf)) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'postgres_conf' => 'The postgres_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $postgresConf = base64_decode($request->postgres_conf);
+ if (mb_detect_encoding($postgresConf, 'ASCII', true) === false) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'postgres_conf' => 'The postgres_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $request->offsetSet('postgres_conf', $postgresConf);
+ }
+ $database = create_standalone_postgresql($environment->id, $destination->uuid, $request->all());
+ if ($instantDeploy) {
+ StartDatabase::dispatch($database);
+ }
+ $database->refresh();
+ $payload = [
+ 'uuid' => $database->uuid,
+ 'internal_db_url' => $database->internal_db_url,
+ ];
+ if ($database->is_public && $database->public_port) {
+ $payload['external_db_url'] = $database->external_db_url;
+ }
+
+ return response()->json(serializeApiResponse($payload))->setStatusCode(201);
+
+ } elseif ($type === NewDatabaseTypes::MARIADB) {
+ $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database'];
+ $validator = customApiValidator($request->all(), [
+ 'clickhouse_admin_user' => 'string',
+ 'clickhouse_admin_password' => '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([
+ 'message' => 'Validation failed.',
+ 'errors' => $errors,
+ ], 422);
+ }
+ removeUnnecessaryFieldsFromRequest($request);
+ if ($request->has('mariadb_conf')) {
+ if (! isBase64Encoded($request->mariadb_conf)) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'mariadb_conf' => 'The mariadb_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $mariadbConf = base64_decode($request->mariadb_conf);
+ if (mb_detect_encoding($mariadbConf, 'ASCII', true) === false) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'mariadb_conf' => 'The mariadb_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $request->offsetSet('mariadb_conf', $mariadbConf);
+ }
+ $database = create_standalone_mariadb($environment->id, $destination->uuid, $request->all());
+ if ($instantDeploy) {
+ StartDatabase::dispatch($database);
+ }
+
+ $database->refresh();
+ $payload = [
+ 'uuid' => $database->uuid,
+ 'internal_db_url' => $database->internal_db_url,
+ ];
+ if ($database->is_public && $database->public_port) {
+ $payload['external_db_url'] = $database->external_db_url;
+ }
+
+ return response()->json(serializeApiResponse($payload))->setStatusCode(201);
+ } elseif ($type === NewDatabaseTypes::MYSQL) {
+ $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_user', 'mysql_database', 'mysql_conf'];
+ $validator = customApiValidator($request->all(), [
+ 'mysql_root_password' => 'string',
+ 'mysql_user' => 'string',
+ 'mysql_database' => 'string',
+ 'mysql_conf' => '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([
+ 'message' => 'Validation failed.',
+ 'errors' => $errors,
+ ], 422);
+ }
+ removeUnnecessaryFieldsFromRequest($request);
+ if ($request->has('mysql_conf')) {
+ if (! isBase64Encoded($request->mysql_conf)) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'mysql_conf' => 'The mysql_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $mysqlConf = base64_decode($request->mysql_conf);
+ if (mb_detect_encoding($mysqlConf, 'ASCII', true) === false) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'mysql_conf' => 'The mysql_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $request->offsetSet('mysql_conf', $mysqlConf);
+ }
+ $database = create_standalone_mysql($environment->id, $destination->uuid, $request->all());
+ if ($instantDeploy) {
+ StartDatabase::dispatch($database);
+ }
+
+ $database->refresh();
+ $payload = [
+ 'uuid' => $database->uuid,
+ 'internal_db_url' => $database->internal_db_url,
+ ];
+ if ($database->is_public && $database->public_port) {
+ $payload['external_db_url'] = $database->external_db_url;
+ }
+
+ return response()->json(serializeApiResponse($payload))->setStatusCode(201);
+ } elseif ($type === NewDatabaseTypes::REDIS) {
+ $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'redis_password', 'redis_conf'];
+ $validator = customApiValidator($request->all(), [
+ 'redis_password' => 'string',
+ 'redis_conf' => '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([
+ 'message' => 'Validation failed.',
+ 'errors' => $errors,
+ ], 422);
+ }
+ removeUnnecessaryFieldsFromRequest($request);
+ if ($request->has('redis_conf')) {
+ if (! isBase64Encoded($request->redis_conf)) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'redis_conf' => 'The redis_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $redisConf = base64_decode($request->redis_conf);
+ if (mb_detect_encoding($redisConf, 'ASCII', true) === false) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'redis_conf' => 'The redis_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $request->offsetSet('redis_conf', $redisConf);
+ }
+ $database = create_standalone_redis($environment->id, $destination->uuid, $request->all());
+ if ($instantDeploy) {
+ StartDatabase::dispatch($database);
+ }
+
+ $database->refresh();
+ $payload = [
+ 'uuid' => $database->uuid,
+ 'internal_db_url' => $database->internal_db_url,
+ ];
+ if ($database->is_public && $database->public_port) {
+ $payload['external_db_url'] = $database->external_db_url;
+ }
+
+ return response()->json(serializeApiResponse($payload))->setStatusCode(201);
+ } elseif ($type === NewDatabaseTypes::DRAGONFLY) {
+ $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'dragonfly_password'];
+ $validator = customApiValidator($request->all(), [
+ 'dragonfly_password' => '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([
+ 'message' => 'Validation failed.',
+ 'errors' => $errors,
+ ], 422);
+ }
+
+ removeUnnecessaryFieldsFromRequest($request);
+ $database = create_standalone_dragonfly($environment->id, $destination->uuid, $request->all());
+ if ($instantDeploy) {
+ StartDatabase::dispatch($database);
+ }
+
+ return response()->json(serializeApiResponse([
+ 'uuid' => $database->uuid,
+ ]))->setStatusCode(201);
+ } elseif ($type === NewDatabaseTypes::KEYDB) {
+ $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'keydb_password', 'keydb_conf'];
+ $validator = customApiValidator($request->all(), [
+ 'keydb_password' => 'string',
+ 'keydb_conf' => '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([
+ 'message' => 'Validation failed.',
+ 'errors' => $errors,
+ ], 422);
+ }
+ removeUnnecessaryFieldsFromRequest($request);
+ if ($request->has('keydb_conf')) {
+ if (! isBase64Encoded($request->keydb_conf)) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'keydb_conf' => 'The keydb_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $keydbConf = base64_decode($request->keydb_conf);
+ if (mb_detect_encoding($keydbConf, 'ASCII', true) === false) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'keydb_conf' => 'The keydb_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $request->offsetSet('keydb_conf', $keydbConf);
+ }
+ $database = create_standalone_keydb($environment->id, $destination->uuid, $request->all());
+ if ($instantDeploy) {
+ StartDatabase::dispatch($database);
+ }
+
+ $database->refresh();
+ $payload = [
+ 'uuid' => $database->uuid,
+ 'internal_db_url' => $database->internal_db_url,
+ ];
+ if ($database->is_public && $database->public_port) {
+ $payload['external_db_url'] = $database->external_db_url;
+ }
+
+ return response()->json(serializeApiResponse($payload))->setStatusCode(201);
+ } elseif ($type === NewDatabaseTypes::CLICKHOUSE) {
+ $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'clickhouse_admin_user', 'clickhouse_admin_password'];
+ $validator = customApiValidator($request->all(), [
+ 'clickhouse_admin_user' => 'string',
+ 'clickhouse_admin_password' => '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([
+ 'message' => 'Validation failed.',
+ 'errors' => $errors,
+ ], 422);
+ }
+ removeUnnecessaryFieldsFromRequest($request);
+ $database = create_standalone_clickhouse($environment->id, $destination->uuid, $request->all());
+ if ($instantDeploy) {
+ StartDatabase::dispatch($database);
+ }
+
+ $database->refresh();
+ $payload = [
+ 'uuid' => $database->uuid,
+ 'internal_db_url' => $database->internal_db_url,
+ ];
+ if ($database->is_public && $database->public_port) {
+ $payload['external_db_url'] = $database->external_db_url;
+ }
+
+ return response()->json(serializeApiResponse($payload))->setStatusCode(201);
+ } elseif ($type === NewDatabaseTypes::MONGODB) {
+ $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database'];
+ $validator = customApiValidator($request->all(), [
+ 'mongo_conf' => 'string',
+ 'mongo_initdb_root_username' => 'string',
+ 'mongo_initdb_root_password' => 'string',
+ 'mongo_initdb_init_database' => '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([
+ 'message' => 'Validation failed.',
+ 'errors' => $errors,
+ ], 422);
+ }
+ removeUnnecessaryFieldsFromRequest($request);
+ if ($request->has('mongo_conf')) {
+ if (! isBase64Encoded($request->mongo_conf)) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'mongo_conf' => 'The mongo_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $mongoConf = base64_decode($request->mongo_conf);
+ if (mb_detect_encoding($mongoConf, 'ASCII', true) === false) {
+ return response()->json([
+ 'message' => 'Validation failed.',
+ 'errors' => [
+ 'mongo_conf' => 'The mongo_conf should be base64 encoded.',
+ ],
+ ], 422);
+ }
+ $request->offsetSet('mongo_conf', $mongoConf);
+ }
+ $database = create_standalone_mongodb($environment->id, $destination->uuid, $request->all());
+ if ($instantDeploy) {
+ StartDatabase::dispatch($database);
+ }
+
+ $database->refresh();
+ $payload = [
+ 'uuid' => $database->uuid,
+ 'internal_db_url' => $database->internal_db_url,
+ ];
+ if ($database->is_public && $database->public_port) {
+ $payload['external_db_url'] = $database->external_db_url;
+ }
+
+ return response()->json(serializeApiResponse($payload))->setStatusCode(201);
+ }
+
+ return response()->json(['message' => 'Invalid database type requested.'], 400);
+ }
+
+ #[OA\Delete(
+ summary: 'Delete',
+ description: 'Delete database by UUID.',
+ path: '/databases/{uuid}',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Databases'],
+ parameters: [
+ new OA\Parameter(
+ name: 'uuid',
+ in: 'path',
+ description: 'UUID of the database.',
+ required: true,
+ schema: new OA\Schema(
+ type: 'string',
+ format: 'uuid',
+ )
+ ),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Database deleted.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'message' => ['type' => 'string', 'example' => 'Database deleted.'],
+ ]
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ public function delete_by_uuid(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ if (! $request->uuid) {
+ return response()->json(['message' => 'UUID is required.'], 404);
+ }
+ $database = queryDatabaseByUuidWithinTeam($request->uuid, $teamId);
+ if (! $database) {
+ return response()->json(['message' => 'Database not found.'], 404);
+ }
+ StopDatabase::dispatch($database);
+ $database->forceDelete();
+
+ return response()->json([
+ 'message' => 'Database deletion request queued.',
+ ]);
+ }
+
+ #[OA\Get(
+ summary: 'Start',
+ description: 'Start database. `Post` request is also accepted.',
+ path: '/databases/{uuid}/start',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Databases'],
+ parameters: [
+ new OA\Parameter(
+ name: 'uuid',
+ in: 'path',
+ description: 'UUID of the database.',
+ required: true,
+ schema: new OA\Schema(
+ type: 'string',
+ format: 'uuid',
+ )
+ ),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Start database.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'message' => ['type' => 'string', 'example' => 'Database starting request queued.'],
+ ])
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ public function action_deploy(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $uuid = $request->route('uuid');
+ if (! $uuid) {
+ return response()->json(['message' => 'UUID is required.'], 400);
+ }
+ $database = queryDatabaseByUuidWithinTeam($request->uuid, $teamId);
+ if (! $database) {
+ return response()->json(['message' => 'Database not found.'], 404);
+ }
+ if (str($database->status)->contains('running')) {
+ return response()->json(['message' => 'Database is already running.'], 400);
+ }
+ StartDatabase::dispatch($database);
+
+ return response()->json(
+ [
+ 'message' => 'Database starting request queued.',
+ ],
+ 200
+ );
+ }
+
+ #[OA\Get(
+ summary: 'Stop',
+ description: 'Stop database. `Post` request is also accepted.',
+ path: '/databases/{uuid}/stop',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Databases'],
+ parameters: [
+ new OA\Parameter(
+ name: 'uuid',
+ in: 'path',
+ description: 'UUID of the database.',
+ required: true,
+ schema: new OA\Schema(
+ type: 'string',
+ format: 'uuid',
+ )
+ ),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Stop database.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'message' => ['type' => 'string', 'example' => 'Database stopping request queued.'],
+ ])
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ public function action_stop(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $uuid = $request->route('uuid');
+ if (! $uuid) {
+ return response()->json(['message' => 'UUID is required.'], 400);
+ }
+ $database = queryDatabaseByUuidWithinTeam($request->uuid, $teamId);
+ if (! $database) {
+ return response()->json(['message' => 'Database not found.'], 404);
+ }
+ if (str($database->status)->contains('stopped') || str($database->status)->contains('exited')) {
+ return response()->json(['message' => 'Database is already stopped.'], 400);
+ }
+ StopDatabase::dispatch($database);
+
+ return response()->json(
+ [
+ 'message' => 'Database stopping request queued.',
+ ],
+ 200
+ );
+ }
+
+ #[OA\Get(
+ summary: 'Restart',
+ description: 'Restart database. `Post` request is also accepted.',
+ path: '/databases/{uuid}/restart',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Databases'],
+ parameters: [
+ new OA\Parameter(
+ name: 'uuid',
+ in: 'path',
+ description: 'UUID of the database.',
+ required: true,
+ schema: new OA\Schema(
+ type: 'string',
+ format: 'uuid',
+ )
+ ),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Restart database.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'message' => ['type' => 'string', 'example' => 'Database restaring request queued.'],
+ ])
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ public function action_restart(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $uuid = $request->route('uuid');
+ if (! $uuid) {
+ return response()->json(['message' => 'UUID is required.'], 400);
+ }
+ $database = queryDatabaseByUuidWithinTeam($request->uuid, $teamId);
+ if (! $database) {
+ return response()->json(['message' => 'Database not found.'], 404);
+ }
+ RestartDatabase::dispatch($database);
+
+ return response()->json(
+ [
+ 'message' => 'Database restarting request queued.',
+ ],
+ 200
+ );
+
+ }
+}
diff --git a/app/Http/Controllers/Api/Deploy.php b/app/Http/Controllers/Api/Deploy.php
deleted file mode 100644
index d510970dd..000000000
--- a/app/Http/Controllers/Api/Deploy.php
+++ /dev/null
@@ -1,233 +0,0 @@
-get();
- $deployments_per_server = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('server_id', $servers->pluck('id'))->get([
- 'id',
- 'application_id',
- 'application_name',
- 'deployment_url',
- 'pull_request_id',
- 'server_name',
- 'server_id',
- 'status',
- ])->sortBy('id')->toArray();
-
- return response()->json(serialize_api_response($deployments_per_server), 200);
- }
-
- public function deployment_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(['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(serialize_api_response($deployment->makeHidden('logs')), 200);
- }
-
- public function deploy(Request $request)
- {
- $teamId = get_team_id_from_token();
- $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);
- }
- if (is_null($teamId)) {
- return invalid_token();
- }
- if ($tags) {
- return $this->by_tags($tags, $teamId, $force);
- } elseif ($uuids) {
- 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);
- }
-
- private function by_uuids(string $uuid, int $teamId, bool $force = false)
- {
- $uuids = explode(',', $uuid);
- $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);
- }
- $deployments = collect();
- $payload = collect();
- foreach ($uuids as $uuid) {
- $resource = getResourceByUuid($uuid, $teamId);
- 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()]);
- } else {
- $deployments->push(['message' => $return_message, 'resource_uuid' => $uuid]);
- }
- }
- }
- if ($deployments->count() > 0) {
- $payload->put('deployments', $deployments->toArray());
-
- return response()->json($payload->toArray(), 200);
- }
-
- return response()->json(['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)
- {
- $tags = explode(',', $tags);
- $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);
- }
- $message = collect([]);
- $deployments = collect();
- $payload = collect();
- foreach ($tags as $tag) {
- $found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first();
- if (! $found_tag) {
- // $message->push("Tag {$tag} not found.");
- continue;
- }
- $applications = $found_tag->applications()->get();
- $services = $found_tag->services()->get();
- if ($applications->count() === 0 && $services->count() === 0) {
- $message->push("No resources found for tag {$tag}.");
-
- continue;
- }
- foreach ($applications as $resource) {
- ['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
- if ($deployment_uuid) {
- $deployments->push(['resource_uuid' => $resource->uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
- }
- $message = $message->merge($return_message);
- }
- foreach ($services as $resource) {
- ['message' => $return_message] = $this->deploy_resource($resource, $force);
- $message = $message->merge($return_message);
- }
- }
- if ($message->count() > 0) {
- $payload->put('message', $message->toArray());
- if ($deployments->count() > 0) {
- $payload->put('details', $deployments->toArray());
- }
-
- return response()->json($payload->toArray(), 200);
- }
-
- return response()->json(['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
- {
- $message = null;
- $deployment_uuid = null;
- if (gettype($resource) !== 'object') {
- return ['message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid];
- }
- $type = $resource?->getMorphClass();
- if ($type === 'App\Models\Application') {
- $deployment_uuid = new Cuid2(7);
- queue_application_deployment(
- application: $resource,
- deployment_uuid: $deployment_uuid,
- force_rebuild: $force,
- );
- $message = "Application {$resource->name} deployment queued.";
- } elseif ($type === 'App\Models\StandalonePostgresql') {
- StartPostgresql::run($resource);
- $resource->update([
- 'started_at' => now(),
- ]);
- $message = "Database {$resource->name} started.";
- } elseif ($type === 'App\Models\StandaloneRedis') {
- StartRedis::run($resource);
- $resource->update([
- 'started_at' => now(),
- ]);
- $message = "Database {$resource->name} started.";
- } elseif ($type === 'App\Models\StandaloneKeydb') {
- StartKeydb::run($resource);
- $resource->update([
- 'started_at' => now(),
- ]);
- $message = "Database {$resource->name} started.";
- } elseif ($type === 'App\Models\StandaloneDragonfly') {
- StartDragonfly::run($resource);
- $resource->update([
- 'started_at' => now(),
- ]);
- $message = "Database {$resource->name} started.";
- } elseif ($type === 'App\Models\StandaloneClickhouse') {
- StartClickhouse::run($resource);
- $resource->update([
- 'started_at' => now(),
- ]);
- $message = "Database {$resource->name} started.";
- } elseif ($type === 'App\Models\StandaloneMongodb') {
- StartMongodb::run($resource);
- $resource->update([
- 'started_at' => now(),
- ]);
- $message = "Database {$resource->name} started.";
- } elseif ($type === 'App\Models\StandaloneMysql') {
- StartMysql::run($resource);
- $resource->update([
- 'started_at' => now(),
- ]);
- $message = "Database {$resource->name} started.";
- } elseif ($type === 'App\Models\StandaloneMariadb') {
- StartMariadb::run($resource);
- $resource->update([
- 'started_at' => now(),
- ]);
- $message = "Database {$resource->name} started.";
- } elseif ($type === 'App\Models\Service') {
- StartService::run($resource);
- $message = "Service {$resource->name} started. It could take a while, be patient.";
- }
-
- return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
- }
-}
diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php
new file mode 100644
index 000000000..2ee56d0cd
--- /dev/null
+++ b/app/Http/Controllers/Api/DeployController.php
@@ -0,0 +1,317 @@
+user()->currentAccessToken();
+ if ($token->can('view:sensitive')) {
+ return serializeApiResponse($deployment);
+ }
+
+ $deployment->makeHidden([
+ 'logs',
+ ]);
+
+ return serializeApiResponse($deployment);
+ }
+
+ #[OA\Get(
+ summary: 'List',
+ description: 'List currently running deployments',
+ path: '/deployments',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Deployments'],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Get all currently running deployments.',
+ content: [
+
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'array',
+ items: new OA\Items(ref: '#/components/schemas/ApplicationDeploymentQueue'),
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function deployments(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $servers = Server::whereTeamId($teamId)->get();
+ $deployments_per_server = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('server_id', $servers->pluck('id'))->get()->sortBy('id');
+ $deployments_per_server = $deployments_per_server->map(function ($deployment) {
+ return $this->removeSensitiveData($deployment);
+ });
+
+ return response()->json($deployments_per_server);
+ }
+
+ #[OA\Get(
+ summary: 'Get',
+ description: 'Get deployment by UUID.',
+ path: '/deployments/{uuid}',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Deployments'],
+ parameters: [
+ new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Deployment Uuid', schema: new OA\Schema(type: 'integer')),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Get deployment by UUID.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ ref: '#/components/schemas/ApplicationDeploymentQueue',
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ public function deployment_by_uuid(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $uuid = $request->route('uuid');
+ if (! $uuid) {
+ return response()->json(['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($this->removeSensitiveData($deployment));
+ }
+
+ #[OA\Get(
+ summary: 'Deploy',
+ description: 'Deploy by tag or uuid. `Post` request also accepted.',
+ path: '/deploy',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Deployments'],
+ parameters: [
+ new OA\Parameter(name: 'tag', in: 'query', description: 'Tag name(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')),
+ new OA\Parameter(name: 'uuid', in: 'query', description: 'Resource UUID(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')),
+ new OA\Parameter(name: 'force', in: 'query', description: 'Force rebuild (without cache)', schema: new OA\Schema(type: 'boolean')),
+ ],
+
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Get deployment(s) Uuid\'s',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'deployments' => new OA\Property(
+ property: 'deployments',
+ type: 'array',
+ items: new OA\Items(
+ type: 'object',
+ properties: [
+ 'message' => ['type' => 'string'],
+ 'resource_uuid' => ['type' => 'string'],
+ 'deployment_uuid' => ['type' => 'string'],
+ ]
+ ),
+ ),
+ ],
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+
+ ]
+ )]
+ public function deploy(Request $request)
+ {
+ $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.'], 400);
+ }
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ if ($tags) {
+ return $this->by_tags($tags, $teamId, $force);
+ } elseif ($uuids) {
+ return $this->by_uuids($uuids, $teamId, $force);
+ }
+
+ return response()->json(['message' => 'You must provide uuid or tag.'], 400);
+ }
+
+ private function by_uuids(string $uuid, int $teamId, bool $force = false)
+ {
+ $uuids = explode(',', $uuid);
+ $uuids = collect(array_filter($uuids));
+
+ if (count($uuids) === 0) {
+ return response()->json(['message' => 'No UUIDs provided.'], 400);
+ }
+ $deployments = collect();
+ $payload = collect();
+ foreach ($uuids as $uuid) {
+ $resource = getResourceByUuid($uuid, $teamId);
+ 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()]);
+ } else {
+ $deployments->push(['message' => $return_message, 'resource_uuid' => $uuid]);
+ }
+ }
+ }
+ if ($deployments->count() > 0) {
+ $payload->put('deployments', $deployments->toArray());
+
+ return response()->json(serializeApiResponse($payload->toArray()));
+ }
+
+ return response()->json(['message' => 'No resources found.'], 404);
+ }
+
+ public function by_tags(string $tags, int $team_id, bool $force = false)
+ {
+ $tags = explode(',', $tags);
+ $tags = collect(array_filter($tags));
+
+ if (count($tags) === 0) {
+ return response()->json(['message' => 'No TAGs provided.'], 400);
+ }
+ $message = collect([]);
+ $deployments = collect();
+ $payload = collect();
+ foreach ($tags as $tag) {
+ $found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first();
+ if (! $found_tag) {
+ // $message->push("Tag {$tag} not found.");
+ continue;
+ }
+ $applications = $found_tag->applications()->get();
+ $services = $found_tag->services()->get();
+ if ($applications->count() === 0 && $services->count() === 0) {
+ $message->push("No resources found for tag {$tag}.");
+
+ continue;
+ }
+ foreach ($applications as $resource) {
+ ['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
+ if ($deployment_uuid) {
+ $deployments->push(['resource_uuid' => $resource->uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
+ }
+ $message = $message->merge($return_message);
+ }
+ foreach ($services as $resource) {
+ ['message' => $return_message] = $this->deploy_resource($resource, $force);
+ $message = $message->merge($return_message);
+ }
+ }
+ if ($message->count() > 0) {
+ $payload->put('message', $message->toArray());
+ if ($deployments->count() > 0) {
+ $payload->put('details', $deployments->toArray());
+ }
+
+ return response()->json(serializeApiResponse($payload->toArray()));
+ }
+
+ return response()->json(['message' => 'No resources found with this tag.'], 404);
+ }
+
+ 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];
+ }
+ switch ($resource?->getMorphClass()) {
+ case 'App\Models\Application':
+ $deployment_uuid = new Cuid2(7);
+ queue_application_deployment(
+ application: $resource,
+ deployment_uuid: $deployment_uuid,
+ force_rebuild: $force,
+ );
+ $message = "Application {$resource->name} deployment queued.";
+ break;
+ case 'App\Models\Service':
+ StartService::run($resource);
+ $message = "Service {$resource->name} started. It could take a while, be patient.";
+ break;
+ default:
+ // Database resource
+ StartDatabase::dispatch($resource);
+ $resource->update([
+ 'started_at' => now(),
+ ]);
+ $message = "Database {$resource->name} started.";
+ break;
+ }
+
+ return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
+ }
+}
diff --git a/app/Http/Controllers/Api/EnvironmentVariables.php b/app/Http/Controllers/Api/EnvironmentVariablesController.php
similarity index 77%
rename from app/Http/Controllers/Api/EnvironmentVariables.php
rename to app/Http/Controllers/Api/EnvironmentVariablesController.php
index d788bdb0c..d127d0525 100644
--- a/app/Http/Controllers/Api/EnvironmentVariables.php
+++ b/app/Http/Controllers/Api/EnvironmentVariablesController.php
@@ -6,33 +6,29 @@ use App\Http\Controllers\Controller;
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) {
return response()->json([
- 'success' => false,
'message' => 'Environment variable not found.',
], 404);
}
$found_app = $env->resource()->whereRelation('environment.project.team', 'id', $teamId)->first();
if (! $found_app) {
return response()->json([
- 'success' => false,
'message' => 'Environment variable not found.',
], 404);
}
$env->delete();
return response()->json([
- 'success' => true,
'message' => 'Environment variable deleted.',
]);
}
diff --git a/app/Http/Controllers/Api/OpenApi.php b/app/Http/Controllers/Api/OpenApi.php
new file mode 100644
index 000000000..59731ef40
--- /dev/null
+++ b/app/Http/Controllers/Api/OpenApi.php
@@ -0,0 +1,51 @@
+ []],
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Returns the version of the application',
+ content: new OA\JsonContent(
+ type: 'string',
+ example: 'v4.0.0',
+ )),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function version(Request $request)
+ {
+ return response(config('version'));
+ }
+
+ #[OA\Get(
+ summary: 'Enable API',
+ description: 'Enable API (only with root permissions).',
+ path: '/enable',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Enable API.',
+ content: new OA\JsonContent(
+ type: 'object',
+ properties: [
+ new OA\Property(property: 'message', type: 'string', example: 'API enabled.'),
+ ]
+ )),
+ new OA\Response(
+ response: 403,
+ description: 'You are not allowed to enable the API.',
+ content: new OA\JsonContent(
+ type: 'object',
+ properties: [
+ new OA\Property(property: 'message', type: 'string', example: 'You are not allowed to enable the API.'),
+ ]
+ )),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function enable_api(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ if ($teamId !== '0') {
+ return response()->json(['message' => 'You are not allowed to enable the API.'], 403);
+ }
+ $settings = InstanceSettings::get();
+ $settings->update(['is_api_enabled' => true]);
+
+ return response()->json(['message' => 'API enabled.'], 200);
+ }
+
+ #[OA\Get(
+ summary: 'Disable API',
+ description: 'Disable API (only with root permissions).',
+ path: '/disable',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Disable API.',
+ content: new OA\JsonContent(
+ type: 'object',
+ properties: [
+ new OA\Property(property: 'message', type: 'string', example: 'API disabled.'),
+ ]
+ )),
+ new OA\Response(
+ response: 403,
+ description: 'You are not allowed to disable the API.',
+ content: new OA\JsonContent(
+ type: 'object',
+ properties: [
+ new OA\Property(property: 'message', type: 'string', example: 'You are not allowed to disable the API.'),
+ ]
+ )),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function disable_api(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ if ($teamId !== '0') {
+ return response()->json(['message' => 'You are not allowed to disable the API.'], 403);
+ }
+ $settings = InstanceSettings::get();
+ $settings->update(['is_api_enabled' => false]);
+
+ return response()->json(['message' => 'API disabled.'], 200);
+ }
+
+ public function feedback(Request $request)
+ {
+ $content = $request->input('content');
+ $webhook_url = config('coolify.feedback_discord_webhook');
+ if ($webhook_url) {
+ Http::post($webhook_url, [
+ 'content' => $content,
+ ]);
+ }
+
+ return response()->json(['message' => 'Feedback sent.'], 200);
+ }
+
+ #[OA\Get(
+ summary: 'Healthcheck',
+ description: 'Healthcheck endpoint.',
+ path: '/healthcheck',
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Healthcheck endpoint.',
+ content: new OA\JsonContent(
+ type: 'string',
+ example: 'OK',
+ )),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function healthcheck(Request $request)
+ {
+ return 'OK';
+ }
+}
diff --git a/app/Http/Controllers/Api/Project.php b/app/Http/Controllers/Api/Project.php
deleted file mode 100644
index baaf1eacb..000000000
--- 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 000000000..b7c3d115d
--- /dev/null
+++ b/app/Http/Controllers/Api/ProjectController.php
@@ -0,0 +1,147 @@
+ []],
+ ],
+ tags: ['Projects'],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Get all projects.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'array',
+ items: new OA\Items(ref: '#/components/schemas/Project')
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function projects(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $projects = Project::whereTeamId($teamId)->select('id', 'name', 'uuid')->get();
+
+ return response()->json(serializeApiResponse($projects),
+ );
+ }
+
+ #[OA\Get(
+ summary: 'Get',
+ description: 'Get project by Uuid.',
+ path: '/projects/{uuid}',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Projects'],
+ parameters: [
+ new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'integer')),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Project details',
+ content: new OA\JsonContent(ref: '#/components/schemas/Project')),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ description: 'Project not found.',
+ ),
+ ]
+ )]
+ 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(['message' => 'Project not found.'], 404);
+ }
+
+ return response()->json(
+ serializeApiResponse($project),
+ );
+ }
+
+ #[OA\Get(
+ summary: 'Environment',
+ description: 'Get environment by name.',
+ path: '/projects/{uuid}/{environment_name}',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Projects'],
+ parameters: [
+ new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'integer')),
+ new OA\Parameter(name: 'environment_name', in: 'path', required: true, description: 'Environment name', schema: new OA\Schema(type: 'string')),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Project details',
+ content: new OA\JsonContent(ref: '#/components/schemas/Environment')),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ 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(['message' => 'Environment not found.'], 404);
+ }
+ $environment = $environment->load(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']);
+
+ return response()->json(serializeApiResponse($environment));
+ }
+}
diff --git a/app/Http/Controllers/Api/Resources.php b/app/Http/Controllers/Api/ResourcesController.php
similarity index 51%
rename from app/Http/Controllers/Api/Resources.php
rename to app/Http/Controllers/Api/ResourcesController.php
index 0d538b62e..ae076bb71 100644
--- a/app/Http/Controllers/Api/Resources.php
+++ b/app/Http/Controllers/Api/ResourcesController.php
@@ -5,14 +5,42 @@ namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Project;
use Illuminate\Http\Request;
+use OpenApi\Attributes as OA;
-class Resources extends Controller
+class ResourcesController extends Controller
{
+ #[OA\Get(
+ summary: 'List',
+ description: 'Get all resources.',
+ path: '/resources',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Resources'],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Get all resources',
+ content: new OA\JsonContent(
+ type: 'string',
+ example: 'Content is very complex. Will be implemented later.',
+ ),
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
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 +62,6 @@ class Resources extends Controller
return $payload;
});
- return response()->json($resources);
+ return response()->json(serializeApiResponse($resources));
}
}
diff --git a/app/Http/Controllers/Api/SecurityController.php b/app/Http/Controllers/Api/SecurityController.php
new file mode 100644
index 000000000..11e8e27ca
--- /dev/null
+++ b/app/Http/Controllers/Api/SecurityController.php
@@ -0,0 +1,372 @@
+user()->currentAccessToken();
+ if ($token->can('view:sensitive')) {
+ return serializeApiResponse($team);
+ }
+ $team->makeHidden([
+ 'private_key',
+ ]);
+
+ return serializeApiResponse($team);
+ }
+
+ #[OA\Get(
+ summary: 'List',
+ description: 'List all private keys.',
+ path: '/security/keys',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Private Keys'],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Get all private keys.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'array',
+ items: new OA\Items(ref: '#/components/schemas/PrivateKey')
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function keys(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $keys = PrivateKey::where('team_id', $teamId)->get();
+
+ return response()->json($this->removeSensitiveData($keys));
+ }
+
+ #[OA\Get(
+ summary: 'Get',
+ description: 'Get key by UUID.',
+ path: '/security/keys/{uuid}',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Private Keys'],
+ parameters: [
+ new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'integer')),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Get all private keys.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'array',
+ items: new OA\Items(ref: '#/components/schemas/PrivateKey')
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ description: 'Private Key not found.',
+ ),
+ ]
+ )]
+ 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([
+ 'message' => 'Private Key not found.',
+ ], 404);
+ }
+
+ return response()->json($this->removeSensitiveData($key));
+ }
+
+ #[OA\Post(
+ summary: 'Create',
+ description: 'Create a new private key.',
+ path: '/security/keys',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Private Keys'],
+ requestBody: new OA\RequestBody(
+ required: true,
+ content: [
+ 'application/json' => new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ required: ['private_key'],
+ properties: [
+ 'name' => ['type' => 'string'],
+ 'description' => ['type' => 'string'],
+ 'private_key' => ['type' => 'string'],
+ ],
+ additionalProperties: false,
+ )
+ ),
+ ]
+ ),
+ responses: [
+ new OA\Response(
+ response: 201,
+ description: 'The created private key\'s UUID.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'uuid' => ['type' => 'string'],
+ ]
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ 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([
+ '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(serializeApiResponse([
+ 'uuid' => $key->uuid,
+ ]))->setStatusCode(201);
+ }
+
+ #[OA\Patch(
+ summary: 'Update',
+ description: 'Update a private key.',
+ path: '/security/keys',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Private Keys'],
+ requestBody: new OA\RequestBody(
+ required: true,
+ content: [
+ 'application/json' => new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ required: ['private_key'],
+ properties: [
+ 'name' => ['type' => 'string'],
+ 'description' => ['type' => 'string'],
+ 'private_key' => ['type' => 'string'],
+ ],
+ additionalProperties: false,
+ )
+ ),
+ ]
+ ),
+ responses: [
+ new OA\Response(
+ response: 201,
+ description: 'The updated private key\'s UUID.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'uuid' => ['type' => 'string'],
+ ]
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ 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([
+ 'message' => 'Validation failed.',
+ 'errors' => $errors,
+ ], 422);
+ }
+ $foundKey = PrivateKey::where('team_id', $teamId)->where('uuid', $request->uuid)->first();
+ if (is_null($foundKey)) {
+ return response()->json([
+ 'message' => 'Private Key not found.',
+ ], 404);
+ }
+ $foundKey->update($request->all());
+
+ return response()->json(serializeApiResponse([
+ 'uuid' => $foundKey->uuid,
+ ]))->setStatusCode(201);
+ }
+
+ #[OA\Delete(
+ summary: 'Delete',
+ description: 'Delete a private key.',
+ path: '/security/keys/{uuid}',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Private Keys'],
+ parameters: [
+ new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'integer')),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Private Key deleted.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'message' => ['type' => 'string', 'example' => 'Private Key deleted.'],
+ ]
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ description: 'Private Key not found.',
+ ),
+ ]
+ )]
+ public function delete_key(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ if (! $request->uuid) {
+ return response()->json(['message' => 'UUID is required.'], 422);
+ }
+
+ $key = PrivateKey::where('team_id', $teamId)->where('uuid', $request->uuid)->first();
+ if (is_null($key)) {
+ return response()->json(['message' => 'Private Key not found.'], 404);
+ }
+ $key->forceDelete();
+
+ return response()->json([
+ 'message' => 'Private Key deleted.',
+ ]);
+ }
+}
diff --git a/app/Http/Controllers/Api/Server.php b/app/Http/Controllers/Api/Server.php
deleted file mode 100644
index 1a58da7b0..000000000
--- a/app/Http/Controllers/Api/Server.php
+++ /dev/null
@@ -1,167 +0,0 @@
-select('id', 'name', 'uuid', 'ip', 'user', 'port')->get()->load(['settings'])->map(function ($server) {
- $server['is_reachable'] = $server->settings->is_reachable;
- $server['is_usable'] = $server->settings->is_usable;
-
- return $server;
- });
-
- return response()->json($servers);
- }
-
- public function server_by_uuid(Request $request)
- {
- $with_resources = $request->query('resources');
- $teamId = get_team_id_from_token();
- if (is_null($teamId)) {
- return invalid_token();
- }
- $server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
- if (is_null($server)) {
- return response()->json(['message' => 'Server not found.'], 404);
- }
- if ($with_resources) {
- $server['resources'] = $server->definedResources()->map(function ($resource) {
- $payload = [
- 'id' => $resource->id,
- 'uuid' => $resource->uuid,
- 'name' => $resource->name,
- 'type' => $resource->type(),
- 'created_at' => $resource->created_at,
- 'updated_at' => $resource->updated_at,
- ];
- if ($resource->type() === 'service') {
- $payload['status'] = $resource->status();
- } else {
- $payload['status'] = $resource->status;
- }
-
- return $payload;
- });
- } else {
- $server->load(['settings']);
- }
-
- return response()->json($server);
- }
-
- public function get_domains_by_server(Request $request)
- {
- $teamId = get_team_id_from_token();
- if (is_null($teamId)) {
- return invalid_token();
- }
- $uuid = $request->query->get('uuid');
- if ($uuid) {
- $domains = Application::getDomainsByUuid($uuid);
-
- return response()->json([
- 'uuid' => $uuid,
- 'domains' => $domains,
- ]);
- }
- $projects = Project::where('team_id', $teamId)->get();
- $domains = collect();
- $applications = $projects->pluck('applications')->flatten();
- $settings = InstanceSettings::get();
- if ($applications->count() > 0) {
- 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('/', '');
- });
- if ($ip === 'host.docker.internal') {
- if ($settings->public_ipv4) {
- $domains->push([
- 'domain' => $fqdn,
- 'ip' => $settings->public_ipv4,
- ]);
- }
- if ($settings->public_ipv6) {
- $domains->push([
- 'domain' => $fqdn,
- 'ip' => $settings->public_ipv6,
- ]);
- }
- if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
- $domains->push([
- 'domain' => $fqdn,
- 'ip' => $ip,
- ]);
- }
- } else {
- $domains->push([
- 'domain' => $fqdn,
- 'ip' => $ip,
- ]);
- }
- }
- }
- $services = $projects->pluck('services')->flatten();
- if ($services->count() > 0) {
- foreach ($services as $service) {
- $service_applications = $service->applications;
- 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('/', '');
- });
- if ($ip === 'host.docker.internal') {
- if ($settings->public_ipv4) {
- $domains->push([
- 'domain' => $fqdn,
- 'ip' => $settings->public_ipv4,
- ]);
- }
- if ($settings->public_ipv6) {
- $domains->push([
- 'domain' => $fqdn,
- 'ip' => $settings->public_ipv6,
- ]);
- }
- if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
- $domains->push([
- 'domain' => $fqdn,
- 'ip' => $ip,
- ]);
- }
- } else {
- $domains->push([
- 'domain' => $fqdn,
- 'ip' => $ip,
- ]);
- }
- }
- }
- }
- }
- $domains = $domains->groupBy('ip')->map(function ($domain) {
- return $domain->pluck('domain')->flatten();
- })->map(function ($domain, $ip) {
- return [
- 'ip' => $ip,
- 'domains' => $domain,
- ];
- })->values();
-
- return response()->json($domains);
- }
-}
diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php
new file mode 100644
index 000000000..247a2519f
--- /dev/null
+++ b/app/Http/Controllers/Api/ServersController.php
@@ -0,0 +1,396 @@
+user()->currentAccessToken();
+ if ($token->can('view:sensitive')) {
+ return serializeApiResponse($settings);
+ }
+ $settings = $settings->makeHidden([
+ 'metrics_token',
+ ]);
+
+ return serializeApiResponse($settings);
+ }
+
+ private function removeSensitiveData($server)
+ {
+ $token = auth()->user()->currentAccessToken();
+ $server->makeHidden([
+ 'id',
+ ]);
+ if ($token->can('view:sensitive')) {
+ return serializeApiResponse($server);
+ }
+
+ return serializeApiResponse($server);
+ }
+
+ #[OA\Get(
+ summary: 'List',
+ description: 'List all servers.',
+ path: '/servers',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Servers'],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Get all servers.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'array',
+ items: new OA\Items(ref: '#/components/schemas/Server')
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function servers(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ 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;
+ $server['is_usable'] = $server->settings->is_usable;
+
+ return $server;
+ });
+ $servers = $servers->map(function ($server) {
+ $settings = $this->removeSensitiveDataFromSettings($server->settings);
+ $server = $this->removeSensitiveData($server);
+ data_set($server, 'settings', $settings);
+
+ return $server;
+ });
+
+ return response()->json($servers);
+ }
+
+ #[OA\Get(
+ summary: 'Get',
+ description: 'Get server by UUID.',
+ path: '/servers/{uuid}',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Servers'],
+ parameters: [
+ new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'integer')),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Get server by UUID',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ ref: '#/components/schemas/Server'
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ public function server_by_uuid(Request $request)
+ {
+ $with_resources = $request->query('resources');
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
+ if (is_null($server)) {
+ return response()->json(['message' => 'Server not found.'], 404);
+ }
+ if ($with_resources) {
+ $server['resources'] = $server->definedResources()->map(function ($resource) {
+ $payload = [
+ 'id' => $resource->id,
+ 'uuid' => $resource->uuid,
+ 'name' => $resource->name,
+ 'type' => $resource->type(),
+ 'created_at' => $resource->created_at,
+ 'updated_at' => $resource->updated_at,
+ ];
+ if ($resource->type() === 'service') {
+ $payload['status'] = $resource->status();
+ } else {
+ $payload['status'] = $resource->status;
+ }
+
+ return $payload;
+ });
+ } else {
+ $server->load(['settings']);
+ }
+
+ $settings = $this->removeSensitiveDataFromSettings($server->settings);
+ $server = $this->removeSensitiveData($server);
+ data_set($server, 'settings', $settings);
+
+ return response()->json(serializeApiResponse($server));
+ }
+
+ #[OA\Get(
+ summary: 'Resources',
+ description: 'Get resources by server.',
+ path: '/servers/{uuid}/resources',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Servers'],
+ parameters: [
+ new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'integer')),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Get resources by server',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'array',
+ items: new OA\Items(
+ type: 'object',
+ properties: [
+ 'id' => ['type' => 'integer'],
+ 'uuid' => ['type' => 'string'],
+ 'name' => ['type' => 'string'],
+ 'type' => ['type' => 'string'],
+ 'created_at' => ['type' => 'string'],
+ 'updated_at' => ['type' => 'string'],
+ 'status' => ['type' => 'string'],
+ ]
+ )
+ )),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function resources_by_server(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
+ if (is_null($server)) {
+ return response()->json(['message' => 'Server not found.'], 404);
+ }
+ $server['resources'] = $server->definedResources()->map(function ($resource) {
+ $payload = [
+ 'id' => $resource->id,
+ 'uuid' => $resource->uuid,
+ 'name' => $resource->name,
+ 'type' => $resource->type(),
+ 'created_at' => $resource->created_at,
+ 'updated_at' => $resource->updated_at,
+ ];
+ if ($resource->type() === 'service') {
+ $payload['status'] = $resource->status();
+ } else {
+ $payload['status'] = $resource->status;
+ }
+
+ return $payload;
+ });
+ $server = $this->removeSensitiveData($server);
+ ray($server);
+
+ return response()->json(serializeApiResponse(data_get($server, 'resources')));
+ }
+
+ #[OA\Get(
+ summary: 'Domains',
+ description: 'Get domains by server.',
+ path: '/servers/{uuid}/domains',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Servers'],
+ parameters: [
+ new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'integer')),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Get domains by server',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'array',
+ items: new OA\Items(
+ type: 'object',
+ properties: [
+ 'ip' => ['type' => 'string'],
+ 'domains' => ['type' => 'array', 'items' => ['type' => 'string']],
+ ]
+ )
+ )),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function domains_by_server(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $uuid = $request->get('uuid');
+ if ($uuid) {
+ $domains = Application::getDomainsByUuid($uuid);
+
+ return response()->json(serializeApiResponse($domains));
+ }
+ $projects = Project::where('team_id', $teamId)->get();
+ $domains = collect();
+ $applications = $projects->pluck('applications')->flatten();
+ $settings = InstanceSettings::get();
+ if ($applications->count() > 0) {
+ foreach ($applications as $application) {
+ $ip = $application->destination->server->ip;
+ $fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
+ $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([
+ 'domain' => $fqdn,
+ 'ip' => $settings->public_ipv4,
+ ]);
+ }
+ if ($settings->public_ipv6) {
+ $domains->push([
+ 'domain' => $fqdn,
+ 'ip' => $settings->public_ipv6,
+ ]);
+ }
+ if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
+ $domains->push([
+ 'domain' => $fqdn,
+ 'ip' => $ip,
+ ]);
+ }
+ } else {
+ $domains->push([
+ 'domain' => $fqdn,
+ 'ip' => $ip,
+ ]);
+ }
+ }
+ }
+ $services = $projects->pluck('services')->flatten();
+ if ($services->count() > 0) {
+ foreach ($services as $service) {
+ $service_applications = $service->applications;
+ if ($service_applications->count() > 0) {
+ foreach ($service_applications as $application) {
+ $fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
+ $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([
+ 'domain' => $fqdn,
+ 'ip' => $settings->public_ipv4,
+ ]);
+ }
+ if ($settings->public_ipv6) {
+ $domains->push([
+ 'domain' => $fqdn,
+ 'ip' => $settings->public_ipv6,
+ ]);
+ }
+ if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
+ $domains->push([
+ 'domain' => $fqdn,
+ 'ip' => $ip,
+ ]);
+ }
+ } else {
+ $domains->push([
+ 'domain' => $fqdn,
+ 'ip' => $ip,
+ ]);
+ }
+ }
+ }
+ }
+ }
+ $domains = $domains->groupBy('ip')->map(function ($domain) {
+ return $domain->pluck('domain')->flatten();
+ })->map(function ($domain, $ip) {
+ return [
+ 'ip' => $ip,
+ 'domains' => $domain,
+ ];
+ })->values();
+
+ return response()->json(serializeApiResponse($domains));
+ }
+}
diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php
new file mode 100644
index 000000000..7d58987d8
--- /dev/null
+++ b/app/Http/Controllers/Api/ServicesController.php
@@ -0,0 +1,702 @@
+user()->currentAccessToken();
+ $service->makeHidden([
+ 'id',
+ ]);
+ if ($token->can('view:sensitive')) {
+ return serializeApiResponse($service);
+ }
+
+ $service->makeHidden([
+ 'docker_compose_raw',
+ 'docker_compose',
+ ]);
+
+ return serializeApiResponse($service);
+ }
+
+ #[OA\Get(
+ summary: 'List',
+ description: 'List all services.',
+ path: '/services',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Services'],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Get all services',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'array',
+ items: new OA\Items(ref: '#/components/schemas/Service')
+ )
+ ),
+ ]
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function services(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $projects = Project::where('team_id', $teamId)->get();
+ $services = collect();
+ foreach ($projects as $project) {
+ $services->push($project->services()->get());
+ }
+ foreach ($services as $service) {
+ $service = $this->removeSensitiveData($service);
+ }
+
+ return response()->json($services->flatten());
+ }
+
+ #[OA\Post(
+ summary: 'Create',
+ description: 'Create a one-click service',
+ path: '/services',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Services'],
+ requestBody: new OA\RequestBody(
+ required: true,
+ content: new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ required: ['server_uuid', 'project_uuid', 'environment_name', 'type'],
+ properties: [
+ 'type' => [
+ 'description' => 'The one-click service type',
+ 'type' => 'string',
+ 'enum' => [
+ 'activepieces',
+ 'appsmith',
+ 'appwrite',
+ 'authentik',
+ 'babybuddy',
+ 'budge',
+ 'changedetection',
+ 'chatwoot',
+ 'classicpress-with-mariadb',
+ 'classicpress-with-mysql',
+ 'classicpress-without-database',
+ 'cloudflared',
+ 'code-server',
+ 'dashboard',
+ 'directus',
+ 'directus-with-postgresql',
+ 'docker-registry',
+ 'docuseal',
+ 'docuseal-with-postgres',
+ 'dokuwiki',
+ 'duplicati',
+ 'emby',
+ 'embystat',
+ 'fider',
+ 'filebrowser',
+ 'firefly',
+ 'formbricks',
+ 'ghost',
+ 'gitea',
+ 'gitea-with-mariadb',
+ 'gitea-with-mysql',
+ 'gitea-with-postgresql',
+ 'glance',
+ 'glances',
+ 'glitchtip',
+ 'grafana',
+ 'grafana-with-postgresql',
+ 'grocy',
+ 'heimdall',
+ 'homepage',
+ 'jellyfin',
+ 'kuzzle',
+ 'listmonk',
+ 'logto',
+ 'mediawiki',
+ 'meilisearch',
+ 'metabase',
+ 'metube',
+ 'minio',
+ 'moodle',
+ 'n8n',
+ 'n8n-with-postgresql',
+ 'next-image-transformation',
+ 'nextcloud',
+ 'nocodb',
+ 'odoo',
+ 'openblocks',
+ 'pairdrop',
+ 'penpot',
+ 'phpmyadmin',
+ 'pocketbase',
+ 'posthog',
+ 'reactive-resume',
+ 'rocketchat',
+ 'shlink',
+ 'slash',
+ 'snapdrop',
+ 'statusnook',
+ 'stirling-pdf',
+ 'supabase',
+ 'syncthing',
+ 'tolgee',
+ 'trigger',
+ 'trigger-with-external-database',
+ 'twenty',
+ 'umami',
+ 'unleash-with-postgresql',
+ 'unleash-without-database',
+ 'uptime-kuma',
+ 'vaultwarden',
+ 'vikunja',
+ 'weblate',
+ 'whoogle',
+ 'wordpress-with-mariadb',
+ 'wordpress-with-mysql',
+ 'wordpress-without-database',
+ ],
+ ],
+ 'name' => ['type' => 'string', 'maxLength' => 255, 'description' => 'Name of the service.'],
+ 'description' => ['type' => 'string', 'nullable' => true, 'description' => 'Description of the service.'],
+ 'project_uuid' => ['type' => 'string', 'description' => 'Project UUID.'],
+ 'environment_name' => ['type' => 'string', 'description' => 'Environment name.'],
+ 'server_uuid' => ['type' => 'string', 'description' => 'Server UUID.'],
+ 'destination_uuid' => ['type' => 'string', 'description' => 'Destination UUID. Required if server has multiple destinations.'],
+ 'instant_deploy' => ['type' => 'boolean', 'default' => false, 'description' => 'Start the service immediately after creation.'],
+ ],
+ ),
+ ),
+ ),
+ responses: [
+ new OA\Response(
+ response: 201,
+ description: 'Create a service.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'uuid' => ['type' => 'string', 'description' => 'Service UUID.'],
+ 'domains' => ['type' => 'array', 'items' => ['type' => 'string'], 'description' => 'Service domains.'],
+ ]
+ )
+ ),
+ ]
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function create_service(Request $request)
+ {
+ $allowedFields = ['type', 'name', 'description', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy'];
+
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+
+ $return = validateIncomingRequest($request);
+ if ($return instanceof \Illuminate\Http\JsonResponse) {
+ return $return;
+ }
+ $validator = customApiValidator($request->all(), [
+ 'type' => 'string|required',
+ 'project_uuid' => 'string|required',
+ 'environment_name' => 'string|required',
+ 'server_uuid' => 'string|required',
+ 'destination_uuid' => 'string',
+ 'name' => 'string|max:255',
+ 'description' => 'string|nullable',
+ '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([
+ 'message' => 'Validation failed.',
+ 'errors' => $errors,
+ ], 422);
+ }
+ $serverUuid = $request->server_uuid;
+ $instantDeploy = $request->instant_deploy ?? false;
+ if ($request->is_public && ! $request->public_port) {
+ $request->offsetSet('is_public', false);
+ }
+ $project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first();
+ if (! $project) {
+ return response()->json(['message' => 'Project not found.'], 404);
+ }
+ $environment = $project->environments()->where('name', $request->environment_name)->first();
+ if (! $environment) {
+ return response()->json(['message' => 'Environment not found.'], 404);
+ }
+ $server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first();
+ if (! $server) {
+ return response()->json(['message' => 'Server not found.'], 404);
+ }
+ $destinations = $server->destinations();
+ if ($destinations->count() == 0) {
+ return response()->json(['message' => 'Server has no destinations.'], 400);
+ }
+ if ($destinations->count() > 1 && ! $request->has('destination_uuid')) {
+ return response()->json(['message' => 'Server has multiple destinations and you do not set destination_uuid.'], 400);
+ }
+ $destination = $destinations->first();
+ $services = get_service_templates();
+ $serviceKeys = $services->keys();
+ if ($serviceKeys->contains($request->type)) {
+ $oneClickServiceName = $request->type;
+ $oneClickService = data_get($services, "$oneClickServiceName.compose");
+ $oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null);
+ if ($oneClickDotEnvs) {
+ $oneClickDotEnvs = str(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/')->filter(function ($value) {
+ return ! empty($value);
+ });
+ }
+ if ($oneClickService) {
+ $service_payload = [
+ 'name' => "$oneClickServiceName-".str()->random(10),
+ 'docker_compose_raw' => base64_decode($oneClickService),
+ 'environment_id' => $environment->id,
+ 'service_type' => $oneClickServiceName,
+ 'server_id' => $server->id,
+ 'destination_id' => $destination->id,
+ 'destination_type' => $destination->getMorphClass(),
+ ];
+ if ($oneClickServiceName === 'cloudflared') {
+ data_set($service_payload, 'connect_to_docker_network', true);
+ }
+ $service = Service::create($service_payload);
+ $service->name = "$oneClickServiceName-".$service->uuid;
+ $service->save();
+ if ($oneClickDotEnvs?->count() > 0) {
+ $oneClickDotEnvs->each(function ($value) use ($service) {
+ $key = str()->before($value, '=');
+ $value = str(str()->after($value, '='));
+ $generatedValue = $value;
+ if ($value->contains('SERVICE_')) {
+ $command = $value->after('SERVICE_')->beforeLast('_');
+ $generatedValue = generateEnvValue($command->value(), $service);
+ }
+ EnvironmentVariable::create([
+ 'key' => $key,
+ 'value' => $generatedValue,
+ 'service_id' => $service->id,
+ 'is_build_time' => false,
+ 'is_preview' => false,
+ ]);
+ });
+ }
+ $service->parse(isNew: true);
+ if ($instantDeploy) {
+ StartService::dispatch($service);
+ }
+ $domains = $service->applications()->get()->pluck('fqdn')->sort();
+ $domains = $domains->map(function ($domain) {
+ return str($domain)->beforeLast(':')->value();
+ });
+
+ return response()->json([
+ 'uuid' => $service->uuid,
+ 'domains' => $domains,
+ ]);
+ }
+
+ return response()->json(['message' => 'Service not found.'], 404);
+ } else {
+ return response()->json(['message' => 'Invalid service type.', 'valid_service_types' => $serviceKeys], 400);
+ }
+
+ return response()->json(['message' => 'Invalid service type.'], 400);
+ }
+
+ #[OA\Get(
+ summary: 'Get',
+ description: 'Get service by UUID.',
+ path: '/services/{uuid}',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Services'],
+ parameters: [
+ new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Service UUID', schema: new OA\Schema(type: 'string')),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Get a service by Uuid.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ ref: '#/components/schemas/Service'
+ )
+ ),
+ ]
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ public function service_by_uuid(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ if (! $request->uuid) {
+ return response()->json(['message' => 'UUID is required.'], 404);
+ }
+ $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
+ if (! $service) {
+ return response()->json(['message' => 'Service not found.'], 404);
+ }
+
+ return response()->json($this->removeSensitiveData($service));
+ }
+
+ #[OA\Delete(
+ summary: 'Delete',
+ description: 'Delete service by UUID.',
+ path: '/services/{uuid}',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Services'],
+ parameters: [
+ new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Service UUID', schema: new OA\Schema(type: 'string')),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Delete a service by Uuid',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'message' => ['type' => 'string', 'example' => 'Service deletion request queued.'],
+ ],
+ )
+ ),
+ ]
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ public function delete_by_uuid(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ if (! $request->uuid) {
+ return response()->json(['message' => 'UUID is required.'], 404);
+ }
+ $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
+ if (! $service) {
+ return response()->json(['message' => 'Service not found.'], 404);
+ }
+ DeleteResourceJob::dispatch($service);
+
+ return response()->json([
+ 'message' => 'Service deletion request queued.',
+ ]);
+ }
+
+ #[OA\Get(
+ summary: 'Start',
+ description: 'Start service. `Post` request is also accepted.',
+ path: '/services/{uuid}/start',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Services'],
+ parameters: [
+ new OA\Parameter(
+ name: 'uuid',
+ in: 'path',
+ description: 'UUID of the service.',
+ required: true,
+ schema: new OA\Schema(
+ type: 'string',
+ format: 'uuid',
+ )
+ ),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Start service.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'message' => ['type' => 'string', 'example' => 'Service starting request queued.'],
+ ])
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ public function action_deploy(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $uuid = $request->route('uuid');
+ if (! $uuid) {
+ return response()->json(['message' => 'UUID is required.'], 400);
+ }
+ $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
+ if (! $service) {
+ return response()->json(['message' => 'Service not found.'], 404);
+ }
+ if (str($service->status())->contains('running')) {
+ return response()->json(['message' => 'Service is already running.'], 400);
+ }
+ StartService::dispatch($service);
+
+ return response()->json(
+ [
+ 'message' => 'Service starting request queued.',
+ ],
+ 200
+ );
+ }
+
+ #[OA\Get(
+ summary: 'Stop',
+ description: 'Stop service. `Post` request is also accepted.',
+ path: '/services/{uuid}/stop',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Services'],
+ parameters: [
+ new OA\Parameter(
+ name: 'uuid',
+ in: 'path',
+ description: 'UUID of the service.',
+ required: true,
+ schema: new OA\Schema(
+ type: 'string',
+ format: 'uuid',
+ )
+ ),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Stop service.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'message' => ['type' => 'string', 'example' => 'Service stopping request queued.'],
+ ])
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ public function action_stop(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $uuid = $request->route('uuid');
+ if (! $uuid) {
+ return response()->json(['message' => 'UUID is required.'], 400);
+ }
+ $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
+ if (! $service) {
+ return response()->json(['message' => 'Service not found.'], 404);
+ }
+ if (str($service->status())->contains('stopped') || str($service->status())->contains('exited')) {
+ return response()->json(['message' => 'Service is already stopped.'], 400);
+ }
+ StopService::dispatch($service);
+
+ return response()->json(
+ [
+ 'message' => 'Service stopping request queued.',
+ ],
+ 200
+ );
+ }
+
+ #[OA\Get(
+ summary: 'Restart',
+ description: 'Restart service. `Post` request is also accepted.',
+ path: '/services/{uuid}/restart',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Services'],
+ parameters: [
+ new OA\Parameter(
+ name: 'uuid',
+ in: 'path',
+ description: 'UUID of the service.',
+ required: true,
+ schema: new OA\Schema(
+ type: 'string',
+ format: 'uuid',
+ )
+ ),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Restart service.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'object',
+ properties: [
+ 'message' => ['type' => 'string', 'example' => 'Service restaring request queued.'],
+ ])
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ public function action_restart(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $uuid = $request->route('uuid');
+ if (! $uuid) {
+ return response()->json(['message' => 'UUID is required.'], 400);
+ }
+ $service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
+ if (! $service) {
+ return response()->json(['message' => 'Service not found.'], 404);
+ }
+ RestartService::dispatch($service);
+
+ return response()->json(
+ [
+ 'message' => 'Service restarting request queued.',
+ ],
+ 200
+ );
+
+ }
+}
diff --git a/app/Http/Controllers/Api/Team.php b/app/Http/Controllers/Api/Team.php
deleted file mode 100644
index c895f2c1b..000000000
--- 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 000000000..1a481e5ec
--- /dev/null
+++ b/app/Http/Controllers/Api/TeamController.php
@@ -0,0 +1,270 @@
+user()->currentAccessToken();
+ $team->makeHidden([
+ 'custom_server_limit',
+ 'pivot',
+ ]);
+ if ($token->can('view:sensitive')) {
+ return serializeApiResponse($team);
+ }
+ $team->makeHidden([
+ 'smtp_username',
+ 'smtp_password',
+ 'resend_api_key',
+ 'telegram_token',
+ ]);
+
+ return serializeApiResponse($team);
+ }
+
+ #[OA\Get(
+ summary: 'List',
+ description: 'Get all teams.',
+ path: '/teams',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Teams'],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'List of teams.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'array',
+ items: new OA\Items(ref: '#/components/schemas/Team')
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function teams(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $teams = auth()->user()->teams->sortBy('id');
+ $teams = $teams->map(function ($team) {
+ return $this->removeSensitiveData($team);
+ });
+
+ return response()->json(
+ $teams,
+ );
+ }
+
+ #[OA\Get(
+ summary: 'Get',
+ description: 'Get team by TeamId.',
+ path: '/teams/{id}',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Teams'],
+ parameters: [
+ new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Team ID', schema: new OA\Schema(type: 'integer')),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'List of teams.',
+ content: new OA\JsonContent(ref: '#/components/schemas/Team')
+ ),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ 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(['message' => 'Team not found.'], 404);
+ }
+ $team = $this->removeSensitiveData($team);
+
+ return response()->json(
+ serializeApiResponse($team),
+ );
+ }
+
+ #[OA\Get(
+ summary: 'Members',
+ description: 'Get members by TeamId.',
+ path: '/teams/{id}/members',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Teams'],
+ parameters: [
+ new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Team ID', schema: new OA\Schema(type: 'integer')),
+ ],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'List of members.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'array',
+ items: new OA\Items(ref: '#/components/schemas/User')
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ new OA\Response(
+ response: 404,
+ ref: '#/components/responses/404',
+ ),
+ ]
+ )]
+ 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(['message' => 'Team not found.'], 404);
+ }
+ $members = $team->members;
+ $members->makeHidden([
+ 'pivot',
+ ]);
+
+ return response()->json(
+ serializeApiResponse($members),
+ );
+ }
+
+ #[OA\Get(
+ summary: 'Authenticated Team',
+ description: 'Get currently authenticated team.',
+ path: '/teams/current',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Teams'],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Current Team.',
+ content: new OA\JsonContent(ref: '#/components/schemas/Team')),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function current_team(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $team = auth()->user()->currentTeam();
+
+ return response()->json(
+ $this->removeSensitiveData($team),
+ );
+ }
+
+ #[OA\Get(
+ summary: 'Authenticated Team Members',
+ description: 'Get currently authenticated team members.',
+ path: '/teams/current/members',
+ security: [
+ ['bearerAuth' => []],
+ ],
+ tags: ['Teams'],
+ responses: [
+ new OA\Response(
+ response: 200,
+ description: 'Currently authenticated team members.',
+ content: [
+ new OA\MediaType(
+ mediaType: 'application/json',
+ schema: new OA\Schema(
+ type: 'array',
+ items: new OA\Items(ref: '#/components/schemas/User')
+ )
+ ),
+ ]),
+ new OA\Response(
+ response: 401,
+ ref: '#/components/responses/401',
+ ),
+ new OA\Response(
+ response: 400,
+ ref: '#/components/responses/400',
+ ),
+ ]
+ )]
+ public function current_team_members(Request $request)
+ {
+ $teamId = getTeamIdFromToken();
+ if (is_null($teamId)) {
+ return invalidTokenResponse();
+ }
+ $team = auth()->user()->currentTeam();
+ $team->members->makeHidden([
+ 'pivot',
+ ]);
+
+ return response()->json(
+ serializeApiResponse($team->members),
+ );
+ }
+}
diff --git a/app/Http/Controllers/Webhook/Stripe.php b/app/Http/Controllers/Webhook/Stripe.php
index 15484f7f3..164322586 100644
--- a/app/Http/Controllers/Webhook/Stripe.php
+++ b/app/Http/Controllers/Webhook/Stripe.php
@@ -54,6 +54,34 @@ class Stripe extends Controller
$type = data_get($event, 'type');
$data = data_get($event, 'data.object');
switch ($type) {
+ case 'radar.early_fraud_warning.created':
+ $stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
+ $id = data_get($data, 'id');
+ $charge = data_get($data, 'charge');
+ if ($charge) {
+ $stripe->refunds->create(['charge' => $charge]);
+ }
+ $pi = data_get($data, 'payment_intent');
+ $piData = $stripe->paymentIntents->retrieve($pi, []);
+ $customerId = data_get($piData, 'customer');
+ $subscription = Subscription::where('stripe_customer_id', $customerId)->first();
+ if (! $subscription) {
+ Sleep::for(5)->seconds();
+ $subscription = Subscription::where('stripe_customer_id', $customerId)->first();
+ }
+ if (! $subscription) {
+ Sleep::for(5)->seconds();
+ $subscription = Subscription::where('stripe_customer_id', $customerId)->first();
+ }
+ if ($subscription) {
+ $subscriptionId = data_get($subscription, 'stripe_subscription_id');
+ $stripe->subscriptions->cancel($subscriptionId, []);
+ $subscription->update([
+ 'stripe_invoice_paid' => false,
+ ]);
+ }
+ send_internal_notification("Early fraud warning created Refunded, subscription canceled. Charge: {$charge}, id: {$id}, pi: {$pi}");
+ break;
case 'checkout.session.completed':
$clientReferenceId = data_get($data, 'client_reference_id');
if (is_null($clientReferenceId)) {
diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php
index e29c4a307..5f1731071 100644
--- a/app/Http/Kernel.php
+++ b/app/Http/Kernel.php
@@ -67,5 +67,7 @@ class Kernel extends HttpKernel
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
+ 'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class,
+ 'ability' => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::class,
];
}
diff --git a/app/Http/Middleware/ApiAllowed.php b/app/Http/Middleware/ApiAllowed.php
new file mode 100644
index 000000000..dc0a433e2
--- /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/Http/Middleware/IgnoreReadOnlyApiToken.php b/app/Http/Middleware/IgnoreReadOnlyApiToken.php
new file mode 100644
index 000000000..bd6cd1f8a
--- /dev/null
+++ b/app/Http/Middleware/IgnoreReadOnlyApiToken.php
@@ -0,0 +1,28 @@
+user()->currentAccessToken();
+ if ($token->can('*')) {
+ return $next($request);
+ }
+ if ($token->can('read-only')) {
+ return response()->json(['message' => 'You are not allowed to perform this action.'], 403);
+ }
+
+ return $next($request);
+ }
+}
diff --git a/app/Http/Middleware/OnlyRootApiToken.php b/app/Http/Middleware/OnlyRootApiToken.php
new file mode 100644
index 000000000..8ff1fa0e5
--- /dev/null
+++ b/app/Http/Middleware/OnlyRootApiToken.php
@@ -0,0 +1,25 @@
+user()->currentAccessToken();
+ if ($token->can('*')) {
+ return $next($request);
+ }
+
+ return response()->json(['message' => 'You are not allowed to perform this action.'], 403);
+ }
+}
diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php
index b53c56d1c..f0f9ac0af 100644
--- a/app/Jobs/ApplicationDeploymentJob.php
+++ b/app/Jobs/ApplicationDeploymentJob.php
@@ -127,7 +127,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
private string $dockerfile_location = '/Dockerfile';
- private string $docker_compose_location = '/docker-compose.yml';
+ private string $docker_compose_location = '/docker-compose.yaml';
private ?string $docker_compose_custom_start_command = null;
@@ -194,6 +194,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
+ if ($this->application->settings->custom_internal_name && ! $this->application->settings->is_consistent_container_name_enabled) {
+ $this->container_name = $this->application->settings->custom_internal_name;
+ }
ray('New container name: ', $this->container_name);
savePrivateKeyToFs($this->server);
@@ -608,10 +611,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
}
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
if ($this->pull_request_id === 0) {
- $composeFileName = "$this->configuration_dir/docker-compose.yml";
+ $composeFileName = "$this->configuration_dir/docker-compose.yaml";
} else {
- $composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml";
- $this->docker_compose_location = "/docker-compose-pr-{$this->pull_request_id}.yml";
+ $composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yaml";
+ $this->docker_compose_location = "/docker-compose-pr-{$this->pull_request_id}.yaml";
}
$this->execute_remote_command(
[
@@ -1570,23 +1573,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
],
],
];
- if (isset($this->application->settings->custom_internal_name)) {
- $docker_compose['services'][$this->container_name]['networks'][$this->destination->network]['aliases'][] = $this->application->settings->custom_internal_name;
- }
- // if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) {
- // if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
- // $docker_compose['services'][$this->container_name]['env_file'][] = '.env';
- // } else {
- // $docker_compose['services'][$this->container_name]['env_file'] = ['.env'];
- // }
- // }
- // if ($this->env_filename) {
- // if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
- // $docker_compose['services'][$this->container_name]['env_file'][] = $this->env_filename;
- // } else {
- // $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename];
- // }
- // }
if (! is_null($this->env_filename)) {
$docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename];
}
@@ -1697,32 +1683,28 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names;
}
- // if ($this->build_pack === 'dockerfile') {
- // $docker_compose['services'][$this->container_name]['build'] = [
- // 'context' => $this->workdir,
- // 'dockerfile' => $this->workdir . $this->dockerfile_location,
- // ];
- // }
if ($this->pull_request_id === 0) {
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
- $docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
- if (count($custom_compose) > 0) {
- $ipv4 = data_get($custom_compose, 'ip.0');
- $ipv6 = data_get($custom_compose, 'ip6.0');
- data_forget($custom_compose, 'ip');
- data_forget($custom_compose, 'ip6');
- if ($ipv4 || $ipv6) {
- data_forget($docker_compose['services'][$this->application->uuid], 'networks');
+ if (! $this->application->settings->custom_internal_name) {
+ $docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
+ if (count($custom_compose) > 0) {
+ $ipv4 = data_get($custom_compose, 'ip.0');
+ $ipv6 = data_get($custom_compose, 'ip6.0');
+ data_forget($custom_compose, 'ip');
+ data_forget($custom_compose, 'ip6');
+ if ($ipv4 || $ipv6) {
+ data_forget($docker_compose['services'][$this->application->uuid], 'networks');
+ }
+ if ($ipv4) {
+ $docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv4_address'] = $ipv4;
+ }
+ if ($ipv6) {
+ $docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv6_address'] = $ipv6;
+ }
+ $docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
}
- if ($ipv4) {
- $docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv4_address'] = $ipv4;
- }
- if ($ipv6) {
- $docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv6_address'] = $ipv6;
- }
- $docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
}
} else {
if (count($custom_compose) > 0) {
@@ -1746,7 +1728,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->docker_compose = Yaml::dump($docker_compose, 10);
$this->docker_compose_base64 = base64_encode($this->docker_compose);
- $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}/docker-compose.yml > /dev/null"), 'hidden' => true]);
+ $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}/docker-compose.yaml > /dev/null"), 'hidden' => true]);
}
private function generate_local_persistent_volumes()
diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php
index 94e0ac3a3..4afe50d53 100644
--- a/app/Jobs/DatabaseBackupJob.php
+++ b/app/Jobs/DatabaseBackupJob.php
@@ -332,8 +332,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
private function backup_standalone_mongodb(string $databaseWithCollections): void
{
try {
- ray($this->database->toArray());
- $url = $this->database->get_db_url(useInternal: true);
+ $url = $this->database->internal_db_url;
if ($databaseWithCollections === 'all') {
$commands[] = 'mkdir -p '.$this->backup_dir;
if (str($this->database->image)->startsWith('mongo:4.0')) {
diff --git a/app/Jobs/DockerCleanupJob.php b/app/Jobs/DockerCleanupJob.php
index e637fb6d4..785940ee6 100644
--- a/app/Jobs/DockerCleanupJob.php
+++ b/app/Jobs/DockerCleanupJob.php
@@ -35,9 +35,9 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
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/Project/Database/Clickhouse/General.php b/app/Livewire/Project/Database/Clickhouse/General.php
index 875a36141..ffdbe95c3 100644
--- a/app/Livewire/Project/Database/Clickhouse/General.php
+++ b/app/Livewire/Project/Database/Clickhouse/General.php
@@ -46,10 +46,8 @@ class General extends Component
public function mount()
{
- $this->db_url = $this->database->get_db_url(true);
- if ($this->database->is_public) {
- $this->db_url_public = $this->database->get_db_url();
- }
+ $this->db_url = $this->database->internal_db_url;
+ $this->db_url_public = $this->database->external_db_url;
$this->server = data_get($this->database, 'destination.server');
}
@@ -87,13 +85,12 @@ class General extends Component
return;
}
StartDatabaseProxy::run($this->database);
- $this->db_url_public = $this->database->get_db_url();
$this->dispatch('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);
- $this->db_url_public = null;
$this->dispatch('success', 'Database is no longer publicly accessible.');
}
+ $this->db_url_public = $this->database->external_db_url;
$this->database->save();
} catch (\Throwable $e) {
$this->database->is_public = ! $this->database->is_public;
diff --git a/app/Livewire/Project/Database/Dragonfly/General.php b/app/Livewire/Project/Database/Dragonfly/General.php
index d6c4eb2ce..f81f4a2f0 100644
--- a/app/Livewire/Project/Database/Dragonfly/General.php
+++ b/app/Livewire/Project/Database/Dragonfly/General.php
@@ -44,10 +44,8 @@ class General extends Component
public function mount()
{
- $this->db_url = $this->database->get_db_url(true);
- if ($this->database->is_public) {
- $this->db_url_public = $this->database->get_db_url();
- }
+ $this->db_url = $this->database->internal_db_url;
+ $this->db_url_public = $this->database->external_db_url;
$this->server = data_get($this->database, 'destination.server');
}
@@ -102,13 +100,12 @@ class General extends Component
return;
}
StartDatabaseProxy::run($this->database);
- $this->db_url_public = $this->database->get_db_url();
$this->dispatch('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);
- $this->db_url_public = null;
$this->dispatch('success', 'Database is no longer publicly accessible.');
}
+ $this->db_url_public = $this->database->external_db_url;
$this->database->save();
} catch (\Throwable $e) {
$this->database->is_public = ! $this->database->is_public;
diff --git a/app/Livewire/Project/Database/Heading.php b/app/Livewire/Project/Database/Heading.php
index ae88ac12b..6435f6781 100644
--- a/app/Livewire/Project/Database/Heading.php
+++ b/app/Livewire/Project/Database/Heading.php
@@ -2,14 +2,8 @@
namespace App\Livewire\Project\Database;
-use App\Actions\Database\StartClickhouse;
-use App\Actions\Database\StartDragonfly;
-use App\Actions\Database\StartKeydb;
-use App\Actions\Database\StartMariadb;
-use App\Actions\Database\StartMongodb;
-use App\Actions\Database\StartMysql;
-use App\Actions\Database\StartPostgresql;
-use App\Actions\Database\StartRedis;
+use App\Actions\Database\RestartDatabase;
+use App\Actions\Database\StartDatabase;
use App\Actions\Database\StopDatabase;
use App\Actions\Docker\GetContainersStatus;
use Livewire\Component;
@@ -47,7 +41,6 @@ class Heading extends Component
public function check_status($showNotification = false)
{
GetContainersStatus::run($this->database->destination->server);
- // dispatch_sync(new ContainerStatusJob($this->database->destination->server));
$this->database->refresh();
if ($showNotification) {
$this->dispatch('success', 'Database status updated.');
@@ -67,32 +60,15 @@ class Heading extends Component
$this->check_status();
}
+ public function restart()
+ {
+ $activity = RestartDatabase::run($this->database);
+ $this->dispatch('activityMonitor', $activity->id);
+ }
+
public function start()
{
- if ($this->database->type() === 'standalone-postgresql') {
- $activity = StartPostgresql::run($this->database);
- $this->dispatch('activityMonitor', $activity->id);
- } elseif ($this->database->type() === 'standalone-redis') {
- $activity = StartRedis::run($this->database);
- $this->dispatch('activityMonitor', $activity->id);
- } elseif ($this->database->type() === 'standalone-mongodb') {
- $activity = StartMongodb::run($this->database);
- $this->dispatch('activityMonitor', $activity->id);
- } elseif ($this->database->type() === 'standalone-mysql') {
- $activity = StartMysql::run($this->database);
- $this->dispatch('activityMonitor', $activity->id);
- } elseif ($this->database->type() === 'standalone-mariadb') {
- $activity = StartMariadb::run($this->database);
- $this->dispatch('activityMonitor', $activity->id);
- } elseif ($this->database->type() === 'standalone-keydb') {
- $activity = StartKeydb::run($this->database);
- $this->dispatch('activityMonitor', $activity->id);
- } elseif ($this->database->type() === 'standalone-dragonfly') {
- $activity = StartDragonfly::run($this->database);
- $this->dispatch('activityMonitor', $activity->id);
- } elseif ($this->database->type() === 'standalone-clickhouse') {
- $activity = StartClickhouse::run($this->database);
- $this->dispatch('activityMonitor', $activity->id);
- }
+ $activity = StartDatabase::run($this->database);
+ $this->dispatch('activityMonitor', $activity->id);
}
}
diff --git a/app/Livewire/Project/Database/Keydb/General.php b/app/Livewire/Project/Database/Keydb/General.php
index 381711946..2b78c9f10 100644
--- a/app/Livewire/Project/Database/Keydb/General.php
+++ b/app/Livewire/Project/Database/Keydb/General.php
@@ -46,10 +46,8 @@ class General extends Component
public function mount()
{
- $this->db_url = $this->database->get_db_url(true);
- if ($this->database->is_public) {
- $this->db_url_public = $this->database->get_db_url();
- }
+ $this->db_url = $this->database->internal_db_url;
+ $this->db_url_public = $this->database->external_db_url;
$this->server = data_get($this->database, 'destination.server');
}
@@ -108,13 +106,12 @@ class General extends Component
return;
}
StartDatabaseProxy::run($this->database);
- $this->db_url_public = $this->database->get_db_url();
$this->dispatch('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);
- $this->db_url_public = null;
$this->dispatch('success', 'Database is no longer publicly accessible.');
}
+ $this->db_url_public = $this->database->external_db_url;
$this->database->save();
} catch (\Throwable $e) {
$this->database->is_public = ! $this->database->is_public;
diff --git a/app/Livewire/Project/Database/Mariadb/General.php b/app/Livewire/Project/Database/Mariadb/General.php
index 8b4b35d11..858d7b383 100644
--- a/app/Livewire/Project/Database/Mariadb/General.php
+++ b/app/Livewire/Project/Database/Mariadb/General.php
@@ -52,10 +52,8 @@ class General extends Component
public function mount()
{
- $this->db_url = $this->database->get_db_url(true);
- if ($this->database->is_public) {
- $this->db_url_public = $this->database->get_db_url();
- }
+ $this->db_url = $this->database->internal_db_url;
+ $this->db_url_public = $this->database->external_db_url;
$this->server = data_get($this->database, 'destination.server');
}
@@ -114,13 +112,12 @@ class General extends Component
return;
}
StartDatabaseProxy::run($this->database);
- $this->db_url_public = $this->database->get_db_url();
$this->dispatch('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);
- $this->db_url_public = null;
$this->dispatch('success', 'Database is no longer publicly accessible.');
}
+ $this->db_url_public = $this->database->external_db_url;
$this->database->save();
} catch (\Throwable $e) {
$this->database->is_public = ! $this->database->is_public;
diff --git a/app/Livewire/Project/Database/Mongodb/General.php b/app/Livewire/Project/Database/Mongodb/General.php
index ee639ae41..5a5ef8a62 100644
--- a/app/Livewire/Project/Database/Mongodb/General.php
+++ b/app/Livewire/Project/Database/Mongodb/General.php
@@ -50,10 +50,8 @@ class General extends Component
public function mount()
{
- $this->db_url = $this->database->get_db_url(true);
- if ($this->database->is_public) {
- $this->db_url_public = $this->database->get_db_url();
- }
+ $this->db_url = $this->database->internal_db_url;
+ $this->db_url_public = $this->database->external_db_url;
$this->server = data_get($this->database, 'destination.server');
}
@@ -115,13 +113,12 @@ class General extends Component
return;
}
StartDatabaseProxy::run($this->database);
- $this->db_url_public = $this->database->get_db_url();
$this->dispatch('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);
- $this->db_url_public = null;
$this->dispatch('success', 'Database is no longer publicly accessible.');
}
+ $this->db_url_public = $this->database->external_db_url;
$this->database->save();
} catch (\Throwable $e) {
$this->database->is_public = ! $this->database->is_public;
diff --git a/app/Livewire/Project/Database/Mysql/General.php b/app/Livewire/Project/Database/Mysql/General.php
index fc0767109..58d8e03a8 100644
--- a/app/Livewire/Project/Database/Mysql/General.php
+++ b/app/Livewire/Project/Database/Mysql/General.php
@@ -52,10 +52,8 @@ class General extends Component
public function mount()
{
- $this->db_url = $this->database->get_db_url(true);
- if ($this->database->is_public) {
- $this->db_url_public = $this->database->get_db_url();
- }
+ $this->db_url = $this->database->internal_db_url;
+ $this->db_url_public = $this->database->external_db_url;
$this->server = data_get($this->database, 'destination.server');
}
@@ -113,13 +111,12 @@ class General extends Component
return;
}
StartDatabaseProxy::run($this->database);
- $this->db_url_public = $this->database->get_db_url();
$this->dispatch('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);
- $this->db_url_public = null;
$this->dispatch('success', 'Database is no longer publicly accessible.');
}
+ $this->db_url_public = $this->database->external_db_url;
$this->database->save();
} catch (\Throwable $e) {
$this->database->is_public = ! $this->database->is_public;
diff --git a/app/Livewire/Project/Database/Postgresql/General.php b/app/Livewire/Project/Database/Postgresql/General.php
index 1c5d39055..eabbbd679 100644
--- a/app/Livewire/Project/Database/Postgresql/General.php
+++ b/app/Livewire/Project/Database/Postgresql/General.php
@@ -27,10 +27,7 @@ class General extends Component
public function getListeners()
{
- $userId = auth()->user()->id;
-
return [
- "echo-private:user.{$userId},DatabaseStatusChanged" => 'database_stopped',
'refresh',
'save_init_script',
'delete_init_script',
@@ -72,18 +69,11 @@ class General extends Component
public function mount()
{
- $this->db_url = $this->database->get_db_url(true);
- if ($this->database->is_public) {
- $this->db_url_public = $this->database->get_db_url();
- }
+ $this->db_url = $this->database->internal_db_url;
+ $this->db_url_public = $this->database->external_db_url;
$this->server = data_get($this->database, 'destination.server');
}
- public function database_stopped()
- {
- $this->dispatch('success', 'Database proxy stopped. Database is no longer publicly accessible.');
- }
-
public function instantSaveAdvanced()
{
try {
@@ -118,13 +108,12 @@ class General extends Component
return;
}
StartDatabaseProxy::run($this->database);
- $this->db_url_public = $this->database->get_db_url();
$this->dispatch('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);
- $this->db_url_public = null;
$this->dispatch('success', 'Database is no longer publicly accessible.');
}
+ $this->db_url_public = $this->database->external_db_url;
$this->database->save();
} catch (\Throwable $e) {
$this->database->is_public = ! $this->database->is_public;
diff --git a/app/Livewire/Project/Database/Redis/General.php b/app/Livewire/Project/Database/Redis/General.php
index b5c1dd881..a7ce0161a 100644
--- a/app/Livewire/Project/Database/Redis/General.php
+++ b/app/Livewire/Project/Database/Redis/General.php
@@ -46,10 +46,8 @@ class General extends Component
public function mount()
{
- $this->db_url = $this->database->get_db_url(true);
- if ($this->database->is_public) {
- $this->db_url_public = $this->database->get_db_url();
- }
+ $this->db_url = $this->database->internal_db_url;
+ $this->db_url_public = $this->database->external_db_url;
$this->server = data_get($this->database, 'destination.server');
}
@@ -102,13 +100,12 @@ class General extends Component
return;
}
StartDatabaseProxy::run($this->database);
- $this->db_url_public = $this->database->get_db_url();
$this->dispatch('success', 'Database is now publicly accessible.');
} else {
StopDatabaseProxy::run($this->database);
- $this->db_url_public = null;
$this->dispatch('success', 'Database is no longer publicly accessible.');
}
+ $this->db_url_public = $this->database->external_db_url;
$this->database->save();
} catch (\Throwable $e) {
$this->database->is_public = ! $this->database->is_public;
diff --git a/app/Livewire/Security/ApiTokens.php b/app/Livewire/Security/ApiTokens.php
index c485a6a3a..ff8679d21 100644
--- a/app/Livewire/Security/ApiTokens.php
+++ b/app/Livewire/Security/ApiTokens.php
@@ -10,6 +10,12 @@ class ApiTokens extends Component
public $tokens = [];
+ public bool $viewSensitiveData = false;
+
+ public bool $readOnly = true;
+
+ public array $permissions = ['read-only'];
+
public function render()
{
return view('livewire.security.api-tokens');
@@ -17,7 +23,33 @@ class ApiTokens extends Component
public function mount()
{
- $this->tokens = auth()->user()->tokens;
+ $this->tokens = auth()->user()->tokens->sortByDesc('created_at');
+ }
+
+ public function updatedViewSensitiveData()
+ {
+ if ($this->viewSensitiveData) {
+ $this->permissions[] = 'view:sensitive';
+ $this->permissions = array_diff($this->permissions, ['*']);
+ } else {
+ $this->permissions = array_diff($this->permissions, ['view:sensitive']);
+ }
+ if (count($this->permissions) == 0) {
+ $this->permissions = ['*'];
+ }
+ }
+
+ public function updatedReadOnly()
+ {
+ if ($this->readOnly) {
+ $this->permissions[] = 'read-only';
+ $this->permissions = array_diff($this->permissions, ['*']);
+ } else {
+ $this->permissions = array_diff($this->permissions, ['read-only']);
+ }
+ if (count($this->permissions) == 0) {
+ $this->permissions = ['*'];
+ }
}
public function addNewToken()
@@ -26,7 +58,13 @@ class ApiTokens extends Component
$this->validate([
'description' => 'required|min:3|max:255',
]);
- $token = auth()->user()->createToken($this->description);
+ // if ($this->viewSensitiveData) {
+ // $this->permissions[] = 'view:sensitive';
+ // }
+ // if ($this->readOnly) {
+ // $this->permissions[] = 'read-only';
+ // }
+ $token = auth()->user()->createToken($this->description, $this->permissions);
$this->tokens = auth()->user()->tokens;
session()->flash('token', $token->plainTextToken);
} catch (\Exception $e) {
diff --git a/app/Livewire/Server/Proxy/Deploy.php b/app/Livewire/Server/Proxy/Deploy.php
index 6d3f00dc8..965c51e2d 100644
--- a/app/Livewire/Server/Proxy/Deploy.php
+++ b/app/Livewire/Server/Proxy/Deploy.php
@@ -84,7 +84,7 @@ class Deploy extends Component
try {
$this->server->proxy->force_stop = false;
$this->server->save();
- $activity = StartProxy::run($this->server);
+ $activity = StartProxy::run($this->server, force: true);
$this->dispatch('activityMonitor', $activity->id, ProxyStatusChanged::class);
} catch (\Throwable $e) {
return handleError($e, $this);
diff --git a/app/Livewire/Settings/Configuration.php b/app/Livewire/Settings/Configuration.php
index 3b6d7cd72..7439e112f 100644
--- a/app/Livewire/Settings/Configuration.php
+++ b/app/Livewire/Settings/Configuration.php
@@ -18,7 +18,8 @@ class Configuration extends Component
public bool $is_dns_validation_enabled;
- // public bool $next_channel;
+ public bool $is_api_enabled;
+
protected string $dynamic_config_path = '/data/coolify/proxy/dynamic';
protected Server $server;
@@ -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,8 +48,8 @@ class Configuration extends Component
$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->is_api_enabled = $this->settings->is_api_enabled;
}
public function instantSave()
@@ -55,12 +58,7 @@ class Configuration extends Component
$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!');
}
@@ -94,6 +92,13 @@ class Configuration extends Component
$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/Application.php b/app/Models/Application.php
index 8b9ff8ee7..47487d1f8 100644
--- a/app/Models/Application.php
+++ b/app/Models/Application.php
@@ -8,12 +8,95 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
+use OpenApi\Attributes as OA;
use RuntimeException;
use Spatie\Activitylog\Models\Activity;
use Spatie\Url\Url;
use Symfony\Component\Yaml\Yaml;
use Visus\Cuid2\Cuid2;
+#[OA\Schema(
+ description: 'Application model',
+ type: 'object',
+ properties: [
+ 'id' => ['type' => 'integer', 'description' => 'The application identifier in the database.'],
+ 'description' => ['type' => 'string', 'nullable' => true, 'description' => 'The application description.'],
+ 'repository_project_id' => ['type' => 'integer', 'nullable' => true, 'description' => 'The repository project identifier.'],
+ 'uuid' => ['type' => 'string', 'description' => 'The application UUID.'],
+ 'name' => ['type' => 'string', 'description' => 'The application name.'],
+ 'fqdn' => ['type' => 'string', 'nullable' => true, 'description' => 'The application domains.'],
+ 'config_hash' => ['type' => 'string', 'description' => 'Configuration hash.'],
+ 'git_repository' => ['type' => 'string', 'description' => 'Git repository URL.'],
+ 'git_branch' => ['type' => 'string', 'description' => 'Git branch.'],
+ 'git_commit_sha' => ['type' => 'string', 'description' => 'Git commit SHA.'],
+ 'git_full_url' => ['type' => 'string', 'nullable' => true, 'description' => 'Git full URL.'],
+ 'docker_registry_image_name' => ['type' => 'string', 'nullable' => true, 'description' => 'Docker registry image name.'],
+ 'docker_registry_image_tag' => ['type' => 'string', 'nullable' => true, 'description' => 'Docker registry image tag.'],
+ 'build_pack' => ['type' => 'string', 'description' => 'Build pack.', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose']],
+ 'static_image' => ['type' => 'string', 'description' => 'Static image used when static site is deployed.'],
+ 'install_command' => ['type' => 'string', 'description' => 'Install command.'],
+ 'build_command' => ['type' => 'string', 'description' => 'Build command.'],
+ 'start_command' => ['type' => 'string', 'description' => 'Start command.'],
+ 'ports_exposes' => ['type' => 'string', 'description' => 'Ports exposes.'],
+ 'ports_mappings' => ['type' => 'string', 'nullable' => true, 'description' => 'Ports mappings.'],
+ 'base_directory' => ['type' => 'string', 'description' => 'Base directory for all commands.'],
+ 'publish_directory' => ['type' => 'string', 'description' => 'Publish directory.'],
+ 'health_check_enabled' => ['type' => 'boolean', 'description' => 'Health check enabled.'],
+ 'health_check_path' => ['type' => 'string', 'description' => 'Health check path.'],
+ 'health_check_port' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check port.'],
+ 'health_check_host' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check host.'],
+ 'health_check_method' => ['type' => 'string', 'description' => 'Health check method.'],
+ 'health_check_return_code' => ['type' => 'integer', 'description' => 'Health check return code.'],
+ 'health_check_scheme' => ['type' => 'string', 'description' => 'Health check scheme.'],
+ 'health_check_response_text' => ['type' => 'string', 'nullable' => true, 'description' => 'Health check response text.'],
+ 'health_check_interval' => ['type' => 'integer', 'description' => 'Health check interval in seconds.'],
+ 'health_check_timeout' => ['type' => 'integer', 'description' => 'Health check timeout in seconds.'],
+ 'health_check_retries' => ['type' => 'integer', 'description' => 'Health check retries count.'],
+ 'health_check_start_period' => ['type' => 'integer', 'description' => 'Health check start period in seconds.'],
+ 'limits_memory' => ['type' => 'string', 'description' => 'Memory limit.'],
+ 'limits_memory_swap' => ['type' => 'string', 'description' => 'Memory swap limit.'],
+ 'limits_memory_swappiness' => ['type' => 'integer', 'description' => 'Memory swappiness.'],
+ 'limits_memory_reservation' => ['type' => 'string', 'description' => 'Memory reservation.'],
+ 'limits_cpus' => ['type' => 'string', 'description' => 'CPU limit.'],
+ 'limits_cpuset' => ['type' => 'string', 'nullable' => true, 'description' => 'CPU set.'],
+ 'limits_cpu_shares' => ['type' => 'integer', 'description' => 'CPU shares.'],
+ 'status' => ['type' => 'string', 'description' => 'Application status.'],
+ 'preview_url_template' => ['type' => 'string', 'description' => 'Preview URL template.'],
+ 'destination_type' => ['type' => 'string', 'description' => 'Destination type.'],
+ 'destination_id' => ['type' => 'integer', 'description' => 'Destination identifier.'],
+ 'source_id' => ['type' => 'integer', 'nullable' => true, 'description' => 'Source identifier.'],
+ 'private_key_id' => ['type' => 'integer', 'nullable' => true, 'description' => 'Private key identifier.'],
+ 'environment_id' => ['type' => 'integer', 'description' => 'Environment identifier.'],
+ 'dockerfile' => ['type' => 'string', 'nullable' => true, 'description' => 'Dockerfile content. Used for dockerfile build pack.'],
+ 'dockerfile_location' => ['type' => 'string', 'description' => 'Dockerfile location.'],
+ 'custom_labels' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom labels.'],
+ 'dockerfile_target_build' => ['type' => 'string', 'nullable' => true, 'description' => 'Dockerfile target build.'],
+ 'manual_webhook_secret_github' => ['type' => 'string', 'nullable' => true, 'description' => 'Manual webhook secret for GitHub.'],
+ 'manual_webhook_secret_gitlab' => ['type' => 'string', 'nullable' => true, 'description' => 'Manual webhook secret for GitLab.'],
+ 'manual_webhook_secret_bitbucket' => ['type' => 'string', 'nullable' => true, 'description' => 'Manual webhook secret for Bitbucket.'],
+ 'manual_webhook_secret_gitea' => ['type' => 'string', 'nullable' => true, 'description' => 'Manual webhook secret for Gitea.'],
+ 'docker_compose_location' => ['type' => 'string', 'description' => 'Docker compose location.'],
+ 'docker_compose' => ['type' => 'string', 'nullable' => true, 'description' => 'Docker compose content. Used for docker compose build pack.'],
+ 'docker_compose_raw' => ['type' => 'string', 'nullable' => true, 'description' => 'Docker compose raw content.'],
+ 'docker_compose_domains' => ['type' => 'string', 'nullable' => true, 'description' => 'Docker compose domains.'],
+ 'docker_compose_custom_start_command' => ['type' => 'string', 'nullable' => true, 'description' => 'Docker compose custom start command.'],
+ 'docker_compose_custom_build_command' => ['type' => 'string', 'nullable' => true, 'description' => 'Docker compose custom build command.'],
+ 'swarm_replicas' => ['type' => 'integer', 'nullable' => true, 'description' => 'Swarm replicas. Only used for swarm deployments.'],
+ 'swarm_placement_constraints' => ['type' => 'string', 'nullable' => true, 'description' => 'Swarm placement constraints. Only used for swarm deployments.'],
+ 'custom_docker_run_options' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom docker run options.'],
+ 'post_deployment_command' => ['type' => 'string', 'nullable' => true, 'description' => 'Post deployment command.'],
+ 'post_deployment_command_container' => ['type' => 'string', 'nullable' => true, 'description' => 'Post deployment command container.'],
+ 'pre_deployment_command' => ['type' => 'string', 'nullable' => true, 'description' => 'Pre deployment command.'],
+ 'pre_deployment_command_container' => ['type' => 'string', 'nullable' => true, 'description' => 'Pre deployment command container.'],
+ 'watch_paths' => ['type' => 'string', 'nullable' => true, 'description' => 'Watch paths.'],
+ 'custom_healthcheck_found' => ['type' => 'boolean', 'description' => 'Custom healthcheck found.'],
+ 'redirect' => ['type' => 'string', 'nullable' => true, 'description' => 'How to set redirect with Traefik / Caddy. www<->non-www.', 'enum' => ['www', 'non-www', 'both']],
+ 'created_at' => ['type' => 'string', 'format' => 'date-time', 'description' => 'The date and time when the application was created.'],
+ 'updated_at' => ['type' => 'string', 'format' => 'date-time', 'description' => 'The date and time when the application was last updated.'],
+ 'deleted_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true, 'description' => 'The date and time when the application was deleted.'],
+ ]
+)]
+
class Application extends BaseModel
{
use SoftDeletes;
diff --git a/app/Models/ApplicationDeploymentQueue.php b/app/Models/ApplicationDeploymentQueue.php
index b1c595046..90d7608cc 100644
--- a/app/Models/ApplicationDeploymentQueue.php
+++ b/app/Models/ApplicationDeploymentQueue.php
@@ -4,7 +4,37 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
+use OpenApi\Attributes as OA;
+#[OA\Schema(
+ description: 'Project model',
+ type: 'object',
+ properties: [
+ 'id' => ['type' => 'integer'],
+ 'application_id' => ['type' => 'string'],
+ 'deployment_uuid' => ['type' => 'string'],
+ 'pull_request_id' => ['type' => 'integer'],
+ 'force_rebuild' => ['type' => 'boolean'],
+ 'commit' => ['type' => 'string'],
+ 'status' => ['type' => 'string'],
+ 'is_webhook' => ['type' => 'boolean'],
+ 'is_api' => ['type' => 'boolean'],
+ 'created_at' => ['type' => 'string'],
+ 'updated_at' => ['type' => 'string'],
+ 'logs' => ['type' => 'string'],
+ 'current_process_id' => ['type' => 'string'],
+ 'restart_only' => ['type' => 'boolean'],
+ 'git_type' => ['type' => 'string'],
+ 'server_id' => ['type' => 'integer'],
+ 'application_name' => ['type' => 'string'],
+ 'server_name' => ['type' => 'string'],
+ 'deployment_url' => ['type' => 'string'],
+ 'destination_id' => ['type' => 'string'],
+ 'only_this_server' => ['type' => 'boolean'],
+ 'rollback' => ['type' => 'boolean'],
+ 'commit_message' => ['type' => 'string'],
+ ],
+)]
class ApplicationDeploymentQueue extends Model
{
protected $guarded = [];
diff --git a/app/Models/Environment.php b/app/Models/Environment.php
index b2bb51092..c892d7ba1 100644
--- a/app/Models/Environment.php
+++ b/app/Models/Environment.php
@@ -4,7 +4,20 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
+use OpenApi\Attributes as OA;
+#[OA\Schema(
+ description: 'Environment model',
+ type: 'object',
+ properties: [
+ 'id' => ['type' => 'integer'],
+ 'name' => ['type' => 'string'],
+ 'project_id' => ['type' => 'integer'],
+ 'created_at' => ['type' => 'string'],
+ 'updated_at' => ['type' => 'string'],
+ 'description' => ['type' => 'string'],
+ ]
+)]
class Environment extends Model
{
protected $guarded = [];
@@ -27,6 +40,9 @@ class Environment extends Model
$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/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php
index 04a556274..1d2a9dc66 100644
--- a/app/Models/EnvironmentVariable.php
+++ b/app/Models/EnvironmentVariable.php
@@ -6,9 +6,33 @@ use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
+use OpenApi\Attributes as OA;
use Symfony\Component\Yaml\Yaml;
use Visus\Cuid2\Cuid2;
+#[OA\Schema(
+ description: 'Environment Variable model',
+ type: 'object',
+ properties: [
+ 'id' => ['type' => 'integer'],
+ 'uuid' => ['type' => 'string'],
+ 'application_id' => ['type' => 'integer'],
+ 'service_id' => ['type' => 'integer'],
+ 'database_id' => ['type' => 'integer'],
+ 'is_build_time' => ['type' => 'boolean'],
+ 'is_literal' => ['type' => 'boolean'],
+ 'is_multiline' => ['type' => 'boolean'],
+ 'is_preview' => ['type' => 'boolean'],
+ 'is_shared' => ['type' => 'boolean'],
+ 'is_shown_once' => ['type' => 'boolean'],
+ 'key' => ['type' => 'string'],
+ 'value' => ['type' => 'string'],
+ 'real_value' => ['type' => 'string'],
+ 'version' => ['type' => 'string'],
+ 'created_at' => ['type' => 'string'],
+ 'updated_at' => ['type' => 'string'],
+ ]
+)]
class EnvironmentVariable extends Model
{
protected $guarded = [];
diff --git a/app/Models/GithubApp.php b/app/Models/GithubApp.php
index daf902daf..66ecdd967 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 @@ class GithubApp extends BaseModel
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 38f79ce75..bd3c41a1f 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/PrivateKey.php b/app/Models/PrivateKey.php
index 187dfca58..45bc6bc84 100644
--- a/app/Models/PrivateKey.php
+++ b/app/Models/PrivateKey.php
@@ -2,8 +2,24 @@
namespace App\Models;
+use OpenApi\Attributes as OA;
use phpseclib3\Crypt\PublicKeyLoader;
+#[OA\Schema(
+ description: 'Private Key model',
+ type: 'object',
+ properties: [
+ 'id' => ['type' => 'integer'],
+ 'uuid' => ['type' => 'string'],
+ 'name' => ['type' => 'string'],
+ 'description' => ['type' => 'string'],
+ 'private_key' => ['type' => 'string', 'format' => 'private-key'],
+ 'is_git_related' => ['type' => 'boolean'],
+ 'team_id' => ['type' => 'integer'],
+ 'created_at' => ['type' => 'string'],
+ 'updated_at' => ['type' => 'string'],
+ ],
+)]
class PrivateKey extends BaseModel
{
protected $fillable = [
@@ -14,6 +30,17 @@ class PrivateKey extends BaseModel
'team_id',
];
+ protected static function booted()
+ {
+ static::saving(function ($key) {
+ $privateKey = data_get($key, 'private_key');
+ if (substr($privateKey, -1) !== "\n") {
+ $key->private_key = $privateKey."\n";
+ }
+ });
+
+ }
+
public static function ownedByCurrentTeam(array $select = ['*'])
{
$selectArray = collect($select)->concat(['id']);
diff --git a/app/Models/Project.php b/app/Models/Project.php
index b7d34e876..d4310e349 100644
--- a/app/Models/Project.php
+++ b/app/Models/Project.php
@@ -2,6 +2,23 @@
namespace App\Models;
+use OpenApi\Attributes as OA;
+
+#[OA\Schema(
+ description: 'Project model',
+ type: 'object',
+ properties: [
+ 'id' => ['type' => 'integer'],
+ 'uuid' => ['type' => 'string'],
+ 'name' => ['type' => 'string'],
+ 'environments' => new OA\Property(
+ property: 'environments',
+ type: 'array',
+ items: new OA\Items(ref: '#/components/schemas/Environment'),
+ description: 'The environments of the project.'
+ ),
+ ]
+)]
class Project extends BaseModel
{
protected $guarded = [];
diff --git a/app/Models/Server.php b/app/Models/Server.php
index 29eedd59f..fc4fd9892 100644
--- a/app/Models/Server.php
+++ b/app/Models/Server.php
@@ -12,11 +12,95 @@ use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Stringable;
+use OpenApi\Attributes as OA;
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
use Spatie\Url\Url;
use Symfony\Component\Yaml\Yaml;
+#[OA\Schema(
+ description: 'Application model',
+ type: 'object',
+ properties: [
+ 'id' => ['type' => 'integer'],
+ 'repository_project_id' => ['type' => 'integer', 'nullable' => true],
+ 'uuid' => ['type' => 'string'],
+ 'name' => ['type' => 'string'],
+ 'fqdn' => ['type' => 'string'],
+ 'config_hash' => ['type' => 'string'],
+ 'git_repository' => ['type' => 'string'],
+ 'git_branch' => ['type' => 'string'],
+ 'git_commit_sha' => ['type' => 'string'],
+ 'git_full_url' => ['type' => 'string', 'nullable' => true],
+ 'docker_registry_image_name' => ['type' => 'string', 'nullable' => true],
+ 'docker_registry_image_tag' => ['type' => 'string', 'nullable' => true],
+ 'build_pack' => ['type' => 'string'],
+ 'static_image' => ['type' => 'string'],
+ 'install_command' => ['type' => 'string'],
+ 'build_command' => ['type' => 'string'],
+ 'start_command' => ['type' => 'string'],
+ 'ports_exposes' => ['type' => 'string'],
+ 'ports_mappings' => ['type' => 'string', 'nullable' => true],
+ 'base_directory' => ['type' => 'string'],
+ 'publish_directory' => ['type' => 'string'],
+ 'health_check_path' => ['type' => 'string'],
+ 'health_check_port' => ['type' => 'string', 'nullable' => true],
+ 'health_check_host' => ['type' => 'string'],
+ 'health_check_method' => ['type' => 'string'],
+ 'health_check_return_code' => ['type' => 'integer'],
+ 'health_check_scheme' => ['type' => 'string'],
+ 'health_check_response_text' => ['type' => 'string', 'nullable' => true],
+ 'health_check_interval' => ['type' => 'integer'],
+ 'health_check_timeout' => ['type' => 'integer'],
+ 'health_check_retries' => ['type' => 'integer'],
+ 'health_check_start_period' => ['type' => 'integer'],
+ 'limits_memory' => ['type' => 'string'],
+ 'limits_memory_swap' => ['type' => 'string'],
+ 'limits_memory_swappiness' => ['type' => 'integer'],
+ 'limits_memory_reservation' => ['type' => 'string'],
+ 'limits_cpus' => ['type' => 'string'],
+ 'limits_cpuset' => ['type' => 'string', 'nullable' => true],
+ 'limits_cpu_shares' => ['type' => 'integer'],
+ 'status' => ['type' => 'string'],
+ 'preview_url_template' => ['type' => 'string'],
+ 'destination_type' => ['type' => 'string'],
+ 'destination_id' => ['type' => 'integer'],
+ 'source_type' => ['type' => 'string'],
+ 'source_id' => ['type' => 'integer'],
+ 'private_key_id' => ['type' => 'integer', 'nullable' => true],
+ 'environment_id' => ['type' => 'integer'],
+ 'created_at' => ['type' => 'string', 'format' => 'date-time'],
+ 'updated_at' => ['type' => 'string', 'format' => 'date-time'],
+ 'description' => ['type' => 'string', 'nullable' => true],
+ 'dockerfile' => ['type' => 'string', 'nullable' => true],
+ 'health_check_enabled' => ['type' => 'boolean'],
+ 'dockerfile_location' => ['type' => 'string'],
+ 'custom_labels' => ['type' => 'string'],
+ 'dockerfile_target_build' => ['type' => 'string', 'nullable' => true],
+ 'manual_webhook_secret_github' => ['type' => 'string', 'nullable' => true],
+ 'manual_webhook_secret_gitlab' => ['type' => 'string', 'nullable' => true],
+ 'docker_compose_location' => ['type' => 'string'],
+ 'docker_compose' => ['type' => 'string', 'nullable' => true],
+ 'docker_compose_raw' => ['type' => 'string', 'nullable' => true],
+ 'docker_compose_domains' => ['type' => 'string', 'nullable' => true],
+ 'deleted_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true],
+ 'docker_compose_custom_start_command' => ['type' => 'string', 'nullable' => true],
+ 'docker_compose_custom_build_command' => ['type' => 'string', 'nullable' => true],
+ 'swarm_replicas' => ['type' => 'integer'],
+ 'swarm_placement_constraints' => ['type' => 'string', 'nullable' => true],
+ 'manual_webhook_secret_bitbucket' => ['type' => 'string', 'nullable' => true],
+ 'custom_docker_run_options' => ['type' => 'string', 'nullable' => true],
+ 'post_deployment_command' => ['type' => 'string', 'nullable' => true],
+ 'post_deployment_command_container' => ['type' => 'string', 'nullable' => true],
+ 'pre_deployment_command' => ['type' => 'string', 'nullable' => true],
+ 'pre_deployment_command_container' => ['type' => 'string', 'nullable' => true],
+ 'watch_paths' => ['type' => 'string', 'nullable' => true],
+ 'custom_healthcheck_found' => ['type' => 'boolean'],
+ 'manual_webhook_secret_gitea' => ['type' => 'string', 'nullable' => true],
+ 'redirect' => ['type' => 'string'],
+ ]
+)]
+
class Server extends BaseModel
{
use SchemalessAttributesTrait;
@@ -496,16 +580,16 @@ $schema://$host {
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/ServerSetting.php b/app/Models/ServerSetting.php
index 9235848ee..c39982b91 100644
--- a/app/Models/ServerSetting.php
+++ b/app/Models/ServerSetting.php
@@ -3,7 +3,46 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
+use OpenApi\Attributes as OA;
+#[OA\Schema(
+ description: 'Server Settings model',
+ type: 'object',
+ properties: [
+ 'id' => ['type' => 'integer'],
+ 'cleanup_after_percentage' => ['type' => 'integer'],
+ 'concurrent_builds' => ['type' => 'integer'],
+ 'dynamic_timeout' => ['type' => 'integer'],
+ 'force_disabled' => ['type' => 'boolean'],
+ 'is_build_server' => ['type' => 'boolean'],
+ 'is_cloudflare_tunnel' => ['type' => 'boolean'],
+ 'is_jump_server' => ['type' => 'boolean'],
+ 'is_logdrain_axiom_enabled' => ['type' => 'boolean'],
+ 'is_logdrain_custom_enabled' => ['type' => 'boolean'],
+ 'is_logdrain_highlight_enabled' => ['type' => 'boolean'],
+ 'is_logdrain_newrelic_enabled' => ['type' => 'boolean'],
+ 'is_metrics_enabled' => ['type' => 'boolean'],
+ 'is_reachable' => ['type' => 'boolean'],
+ 'is_server_api_enabled' => ['type' => 'boolean'],
+ 'is_swarm_manager' => ['type' => 'boolean'],
+ 'is_swarm_worker' => ['type' => 'boolean'],
+ 'is_usable' => ['type' => 'boolean'],
+ 'logdrain_axiom_api_key' => ['type' => 'string'],
+ 'logdrain_axiom_dataset_name' => ['type' => 'string'],
+ 'logdrain_custom_config' => ['type' => 'string'],
+ 'logdrain_custom_config_parser' => ['type' => 'string'],
+ 'logdrain_highlight_project_id' => ['type' => 'string'],
+ 'logdrain_newrelic_base_uri' => ['type' => 'string'],
+ 'logdrain_newrelic_license_key' => ['type' => 'string'],
+ 'metrics_history_days' => ['type' => 'integer'],
+ 'metrics_refresh_rate_seconds' => ['type' => 'integer'],
+ 'metrics_token' => ['type' => 'string'],
+ 'server_id' => ['type' => 'integer'],
+ 'wildcard_domain' => ['type' => 'string'],
+ 'created_at' => ['type' => 'string'],
+ 'updated_at' => ['type' => 'string'],
+ ]
+)]
class ServerSetting extends Model
{
protected $guarded = [];
diff --git a/app/Models/Service.php b/app/Models/Service.php
index cc6a20749..2fc0778e6 100644
--- a/app/Models/Service.php
+++ b/app/Models/Service.php
@@ -6,8 +6,31 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection;
+use OpenApi\Attributes as OA;
use Symfony\Component\Yaml\Yaml;
+#[OA\Schema(
+ description: 'Service model',
+ type: 'object',
+ properties: [
+ 'id' => ['type' => 'integer', 'description' => 'The unique identifier of the service. Only used for database identification.'],
+ 'uuid' => ['type' => 'string', 'description' => 'The unique identifier of the service.'],
+ 'name' => ['type' => 'string', 'description' => 'The name of the service.'],
+ 'environment_id' => ['type' => 'integer', 'description' => 'The unique identifier of the environment where the service is attached to.'],
+ 'server_id' => ['type' => 'integer', 'description' => 'The unique identifier of the server where the service is running.'],
+ 'description' => ['type' => 'string', 'description' => 'The description of the service.'],
+ 'docker_compose_raw' => ['type' => 'string', 'description' => 'The raw docker-compose.yml file of the service.'],
+ 'docker_compose' => ['type' => 'string', 'description' => 'The docker-compose.yml file that is parsed and modified by Coolify.'],
+ 'destination_id' => ['type' => 'integer', 'description' => 'The unique identifier of the destination where the service is running.'],
+ 'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
+ 'is_container_label_escape_enabled' => ['type' => 'boolean', 'description' => 'The flag to enable the container label escape.'],
+ 'config_hash' => ['type' => 'string', 'description' => 'The hash of the service configuration.'],
+ 'service_type' => ['type' => 'string', 'description' => 'The type of the service.'],
+ 'created_at' => ['type' => 'string', 'description' => 'The date and time when the service was created.'],
+ 'updated_at' => ['type' => 'string', 'description' => 'The date and time when the service was last updated.'],
+ 'deleted_at' => ['type' => 'string', 'description' => 'The date and time when the service was deleted.'],
+ ],
+)]
class Service extends BaseModel
{
use HasFactory, SoftDeletes;
diff --git a/app/Models/ServiceApplication.php b/app/Models/ServiceApplication.php
index 98c1cf4e7..6690f254e 100644
--- a/app/Models/ServiceApplication.php
+++ b/app/Models/ServiceApplication.php
@@ -27,6 +27,11 @@ class ServiceApplication extends BaseModel
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 e968db18d..718fc9927 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,18 +180,36 @@ class StandaloneClickhouse extends BaseModel
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
{
- if ($this->is_public && ! $useInternal) {
- return "clickhouse://{$this->clickhouse_user}:{$this->clickhouse_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->clickhouse_db}";
- } else {
- return "clickhouse://{$this->clickhouse_user}:{$this->clickhouse_password}@{$this->uuid}:9000/{$this->clickhouse_db}";
- }
+ return new Attribute(
+ get: fn () => "clickhouse://{$this->clickhouse_admin_user}:{$this->clickhouse_admin_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_admin_user}:{$this->clickhouse_admin_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->clickhouse_db}";
+ }
+
+ return null;
+ }
+ );
}
public function environment()
diff --git a/app/Models/StandaloneDragonfly.php b/app/Models/StandaloneDragonfly.php
index c6718acfe..b8d16d512 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,18 +180,36 @@ class StandaloneDragonfly extends BaseModel
);
}
+ 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
{
- if ($this->is_public && ! $useInternal) {
- return "redis://:{$this->dragonfly_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
- } else {
- return "redis://:{$this->dragonfly_password}@{$this->uuid}:6379/0";
- }
+ 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 environment()
diff --git a/app/Models/StandaloneKeydb.php b/app/Models/StandaloneKeydb.php
index 142f960aa..d2963cf02 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,18 +180,36 @@ class StandaloneKeydb extends BaseModel
);
}
+ 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
{
- if ($this->is_public && ! $useInternal) {
- return "redis://{$this->keydb_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
- } else {
- return "redis://{$this->keydb_password}@{$this->uuid}:6379/0";
- }
+ 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 environment()
diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php
index 7e6d2e0d1..b7907f251 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 @@ class StandaloneMariadb extends BaseModel
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,13 +192,24 @@ class StandaloneMariadb extends BaseModel
);
}
- public function get_db_url(bool $useInternal = false): string
+ protected function internalDbUrl(): Attribute
{
- if ($this->is_public && ! $useInternal) {
- return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mariadb_database}";
- } else {
- return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->uuid}:3306/{$this->mariadb_database}";
- }
+ 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 environment()
diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php
index df895bb34..0f9f9a426 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,18 +200,36 @@ class StandaloneMongodb extends BaseModel
);
}
+ public function databaseType(): Attribute
+ {
+ return new Attribute(
+ get: fn () => $this->type(),
+ );
+ }
+
public function type(): string
{
return 'standalone-mongodb';
}
- public function get_db_url(bool $useInternal = false)
+ protected function internalDbUrl(): Attribute
{
- 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";
- } else {
- return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->uuid}:27017/?directConnection=true";
- }
+ 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 environment()
diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php
index bd160f877..bc4de88ee 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 @@ class StandaloneMysql extends BaseModel
return null;
}
+ public function databaseType(): Attribute
+ {
+ return new Attribute(
+ get: fn () => $this->type(),
+ );
+ }
+
public function type(): string
{
return 'standalone-mysql';
@@ -184,13 +193,24 @@ class StandaloneMysql extends BaseModel
);
}
- public function get_db_url(bool $useInternal = false): string
+ protected function internalDbUrl(): Attribute
{
- if ($this->is_public && ! $useInternal) {
- return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mysql_database}";
- } else {
- return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->uuid}:3306/{$this->mysql_database}";
- }
+ 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 environment()
diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php
index 114d376e8..372d79fd8 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,18 +181,36 @@ class StandalonePostgresql extends BaseModel
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
{
- if ($this->is_public && ! $useInternal) {
- return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->postgres_db}";
- } else {
- return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->uuid}:5432/{$this->postgres_db}";
- }
+ 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 environment()
diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php
index 022cd8d09..64731a28b 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,13 +181,31 @@ class StandaloneRedis extends BaseModel
return 'standalone-redis';
}
- public function get_db_url(bool $useInternal = false): string
+ public function databaseType(): Attribute
{
- if ($this->is_public && ! $useInternal) {
- return "redis://:{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
- } else {
- return "redis://:{$this->redis_password}@{$this->uuid}:6379/0";
- }
+ 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 environment()
diff --git a/app/Models/Team.php b/app/Models/Team.php
index fe5995a1b..3f8e97bc5 100644
--- a/app/Models/Team.php
+++ b/app/Models/Team.php
@@ -7,7 +7,66 @@ use App\Notifications\Channels\SendsEmail;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
+use OpenApi\Attributes as OA;
+#[OA\Schema(
+ description: 'Team model',
+ type: 'object',
+ properties: [
+ 'id' => ['type' => 'integer', 'description' => 'The unique identifier of the team.'],
+ 'name' => ['type' => 'string', 'description' => 'The name of the team.'],
+ 'description' => ['type' => 'string', 'description' => 'The description of the team.'],
+ 'personal_team' => ['type' => 'boolean', 'description' => 'Whether the team is personal or not.'],
+ 'created_at' => ['type' => 'string', 'description' => 'The date and time the team was created.'],
+ 'updated_at' => ['type' => 'string', 'description' => 'The date and time the team was last updated.'],
+ 'smtp_enabled' => ['type' => 'boolean', 'description' => 'Whether SMTP is enabled or not.'],
+ 'smtp_from_address' => ['type' => 'string', 'description' => 'The email address to send emails from.'],
+ 'smtp_from_name' => ['type' => 'string', 'description' => 'The name to send emails from.'],
+ 'smtp_recipients' => ['type' => 'string', 'description' => 'The email addresses to send emails to.'],
+ 'smtp_host' => ['type' => 'string', 'description' => 'The SMTP host.'],
+ 'smtp_port' => ['type' => 'string', 'description' => 'The SMTP port.'],
+ 'smtp_encryption' => ['type' => 'string', 'description' => 'The SMTP encryption.'],
+ 'smtp_username' => ['type' => 'string', 'description' => 'The SMTP username.'],
+ 'smtp_password' => ['type' => 'string', 'description' => 'The SMTP password.'],
+ 'smtp_timeout' => ['type' => 'string', 'description' => 'The SMTP timeout.'],
+ 'smtp_notifications_test' => ['type' => 'boolean', 'description' => 'Whether to send test notifications via SMTP.'],
+ 'smtp_notifications_deployments' => ['type' => 'boolean', 'description' => 'Whether to send deployment notifications via SMTP.'],
+ 'smtp_notifications_status_changes' => ['type' => 'boolean', 'description' => 'Whether to send status change notifications via SMTP.'],
+ 'smtp_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via SMTP.'],
+ 'smtp_notifications_database_backups' => ['type' => 'boolean', 'description' => 'Whether to send database backup notifications via SMTP.'],
+ 'discord_enabled' => ['type' => 'boolean', 'description' => 'Whether Discord is enabled or not.'],
+ 'discord_webhook_url' => ['type' => 'string', 'description' => 'The Discord webhook URL.'],
+ 'discord_notifications_test' => ['type' => 'boolean', 'description' => 'Whether to send test notifications via Discord.'],
+ 'discord_notifications_deployments' => ['type' => 'boolean', 'description' => 'Whether to send deployment notifications via Discord.'],
+ 'discord_notifications_status_changes' => ['type' => 'boolean', 'description' => 'Whether to send status change notifications via Discord.'],
+ 'discord_notifications_database_backups' => ['type' => 'boolean', 'description' => 'Whether to send database backup notifications via Discord.'],
+ 'discord_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via Discord.'],
+ 'show_boarding' => ['type' => 'boolean', 'description' => 'Whether to show the boarding screen or not.'],
+ 'resend_enabled' => ['type' => 'boolean', 'description' => 'Whether to enable resending or not.'],
+ 'resend_api_key' => ['type' => 'string', 'description' => 'The resending API key.'],
+ 'use_instance_email_settings' => ['type' => 'boolean', 'description' => 'Whether to use instance email settings or not.'],
+ 'telegram_enabled' => ['type' => 'boolean', 'description' => 'Whether Telegram is enabled or not.'],
+ 'telegram_token' => ['type' => 'string', 'description' => 'The Telegram token.'],
+ 'telegram_chat_id' => ['type' => 'string', 'description' => 'The Telegram chat ID.'],
+ 'telegram_notifications_test' => ['type' => 'boolean', 'description' => 'Whether to send test notifications via Telegram.'],
+ 'telegram_notifications_deployments' => ['type' => 'boolean', 'description' => 'Whether to send deployment notifications via Telegram.'],
+ 'telegram_notifications_status_changes' => ['type' => 'boolean', 'description' => 'Whether to send status change notifications via Telegram.'],
+ 'telegram_notifications_database_backups' => ['type' => 'boolean', 'description' => 'Whether to send database backup notifications via Telegram.'],
+ 'telegram_notifications_test_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram test message thread ID.'],
+ 'telegram_notifications_deployments_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram deployment message thread ID.'],
+ 'telegram_notifications_status_changes_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram status change message thread ID.'],
+ 'telegram_notifications_database_backups_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram database backup message thread ID.'],
+ 'custom_server_limit' => ['type' => 'string', 'description' => 'The custom server limit.'],
+ 'telegram_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via Telegram.'],
+ 'telegram_notifications_scheduled_tasks_thread_id' => ['type' => 'string', 'description' => 'The Telegram scheduled task message thread ID.'],
+ 'members' => new OA\Property(
+ property: 'members',
+ type: 'array',
+ items: new OA\Items(ref: '#/components/schemas/User'),
+ description: 'The members of the team.'
+ ),
+ ]
+)]
class Team extends Model implements SendsDiscord, SendsEmail
{
use Notifiable;
diff --git a/app/Models/User.php b/app/Models/User.php
index 1e120e951..18d15c0e0 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -17,7 +17,23 @@ use Illuminate\Support\Str;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Sanctum\HasApiTokens;
use Laravel\Sanctum\NewAccessToken;
+use OpenApi\Attributes as OA;
+#[OA\Schema(
+ description: 'User model',
+ type: 'object',
+ properties: [
+ 'id' => ['type' => 'integer', 'description' => 'The user identifier in the database.'],
+ 'name' => ['type' => 'string', 'description' => 'The user name.'],
+ 'email' => ['type' => 'string', 'description' => 'The user email.'],
+ 'email_verified_at' => ['type' => 'string', 'description' => 'The date when the user email was verified.'],
+ 'created_at' => ['type' => 'string', 'description' => 'The date when the user was created.'],
+ 'updated_at' => ['type' => 'string', 'description' => 'The date when the user was updated.'],
+ 'two_factor_confirmed_at' => ['type' => 'string', 'description' => 'The date when the user two factor was confirmed.'],
+ 'force_password_reset' => ['type' => 'boolean', 'description' => 'The flag to force the user to reset the password.'],
+ 'marketing_emails' => ['type' => 'boolean', 'description' => 'The flag to receive marketing emails.'],
+ ],
+)]
class User extends Authenticatable implements SendsEmail
{
use HasApiTokens, HasFactory, Notifiable, TwoFactorAuthenticatable;
diff --git a/bootstrap/helpers/api.php b/bootstrap/helpers/api.php
index c278a5045..8e14ef9ee 100644
--- a/bootstrap/helpers/api.php
+++ b/bootstrap/helpers/api.php
@@ -1,38 +1,178 @@
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(['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) {
- unset($data['created_at']);
- $data['created_at'] = $created_at;
+ if ($data instanceof Collection) {
+ $data = $data->map(function ($d) {
+ $d = collect($d)->sortKeys();
+ $created_at = data_get($d, 'created_at');
+ $updated_at = data_get($d, 'updated_at');
+ if ($created_at) {
+ unset($d['created_at']);
+ $d['created_at'] = $created_at;
- }
- if ($updated_at) {
- unset($data['updated_at']);
- $data['updated_at'] = $updated_at;
- }
- if (data_get($data, 'id')) {
- $data = $data->prepend($data['id'], 'id');
- }
+ }
+ if ($updated_at) {
+ unset($d['updated_at']);
+ $d['updated_at'] = $updated_at;
+ }
+ if (data_get($d, 'name')) {
+ $d = $d->prepend($d['name'], 'name');
+ }
+ if (data_get($d, 'description')) {
+ $d = $d->prepend($d['description'], 'description');
+ }
+ if (data_get($d, 'uuid')) {
+ $d = $d->prepend($d['uuid'], 'uuid');
+ }
- return $data;
+ if (! is_null(data_get($d, 'id'))) {
+ $d = $d->prepend($d['id'], 'id');
+ }
+
+ return $d;
+ });
+
+ return $data;
+ } else {
+ $d = collect($data)->sortKeys();
+ $created_at = data_get($d, 'created_at');
+ $updated_at = data_get($d, 'updated_at');
+ if ($created_at) {
+ unset($d['created_at']);
+ $d['created_at'] = $created_at;
+
+ }
+ if ($updated_at) {
+ unset($d['updated_at']);
+ $d['updated_at'] = $updated_at;
+ }
+ if (data_get($d, 'name')) {
+ $d = $d->prepend($d['name'], 'name');
+ }
+ if (data_get($d, 'description')) {
+ $d = $d->prepend($d['description'], 'description');
+ }
+ if (data_get($d, 'uuid')) {
+ $d = $d->prepend($d['uuid'], 'uuid');
+ }
+
+ if (! is_null(data_get($d, 'id'))) {
+ $d = $d->prepend($d['id'], 'id');
+ }
+
+ return $d;
+ }
+}
+
+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',
+ 'docker_compose_location' => 'string',
+ 'docker_compose' => 'string|nullable',
+ 'docker_compose_raw' => 'string|nullable',
+ 'docker_compose_domains' => 'array|nullable',
+ 'docker_compose_custom_start_command' => 'string|nullable',
+ 'docker_compose_custom_build_command' => 'string|nullable',
+ ];
+}
+
+function validateIncomingRequest(Request $request)
+{
+ // check if request is json
+ if (! $request->isJson()) {
+ return response()->json([
+ '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([
+ 'message' => 'Invalid request.',
+ 'error' => 'Invalid JSON.',
+ ], 400);
+ }
+ // check if valid json is empty
+ if (empty($request->json()->all())) {
+ return response()->json([
+ 'message' => 'Invalid request.',
+ 'error' => 'Empty 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 dba8aa543..089298956 100644
--- a/bootstrap/helpers/databases.php
+++ b/bootstrap/helpers/databases.php
@@ -19,136 +19,165 @@ 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();
- 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(),
- ]);
+ if ($otherData) {
+ $database->fill($otherData);
+ }
+ $database->save();
+
+ 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;
}
-/**
- * Delete file locally on the filesystem.
- */
function delete_backup_locally(?string $filename, Server $server): void
{
if (empty($filename)) {
@@ -156,3 +185,17 @@ function delete_backup_locally(?string $filename, Server $server): void
}
instant_remote_process(["rm -f \"{$filename}\""], $server, throwError: false);
}
+
+function isPublicPortAlreadyUsed(Server $server, int $port, ?string $id = null): bool
+{
+ if ($id) {
+ $foundDatabase = $server->databases()->where('public_port', $port)->where('is_public', true)->where('id', '!=', $id)->first();
+ } else {
+ $foundDatabase = $server->databases()->where('public_port', $port)->where('is_public', true)->first();
+ }
+ if ($foundDatabase) {
+ return true;
+ }
+
+ return false;
+}
diff --git a/bootstrap/helpers/services.php b/bootstrap/helpers/services.php
index ad2268a8e..9140ca8c8 100644
--- a/bootstrap/helpers/services.php
+++ b/bootstrap/helpers/services.php
@@ -199,3 +199,10 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
return handleError($e);
}
}
+function serviceKeys()
+{
+ $services = get_service_templates();
+ $serviceKeys = $services->keys();
+
+ return $serviceKeys;
+}
diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php
index a4676cfd4..cf12d1195 100644
--- a/bootstrap/helpers/shared.php
+++ b/bootstrap/helpers/shared.php
@@ -536,6 +536,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;
@@ -1347,9 +1384,11 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
$parsedServiceVariables->put('COOLIFY_CONTAINER_NAME', "$serviceName-{$resource->uuid}");
$parsedServiceVariables = $parsedServiceVariables->map(function ($value, $key) use ($envs_from_coolify) {
- $found_env = $envs_from_coolify->where('key', $key)->first();
- if ($found_env) {
- return $found_env->value;
+ if (! str($value)->startsWith('$')) {
+ $found_env = $envs_from_coolify->where('key', $key)->first();
+ if ($found_env) {
+ return $found_env->value;
+ }
}
return $value;
@@ -2129,6 +2168,75 @@ function ip_match($ip, $cidrs, &$match = null)
return false;
}
+function checkIfDomainIsAlreadyUsed(Collection|array $domains, ?string $teamId, string $uuid)
+{
+ 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', 'uuid'])->filter(fn ($app) => $app->uuid !== $uuid);
+ $serviceApplications = ServiceApplication::ownedByCurrentTeamAPI($teamId)->get(['fqdn', 'uuid'])->filter(fn ($app) => $app->uuid !== $uuid);
+ $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 (str($app->fqdn)->isEmpty()) {
+ 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/composer.json b/composer.json
index 3603b936e..87ea72472 100644
--- a/composer.json
+++ b/composer.json
@@ -13,10 +13,10 @@
"doctrine/dbal": "^3.6",
"guzzlehttp/guzzle": "^7.5.0",
"laravel/fortify": "^v1.16.0",
- "laravel/framework": "^v10.7.1",
+ "laravel/framework": "^v11",
"laravel/horizon": "^5.23.1",
"laravel/prompts": "^0.1.6",
- "laravel/sanctum": "^v3.2.1",
+ "laravel/sanctum": "^v4.0",
"laravel/socialite": "^v5.14.0",
"laravel/tinker": "^v2.8.1",
"laravel/ui": "^4.2",
@@ -32,8 +32,7 @@
"poliander/cron": "^3.0",
"purplepixie/phpdns": "^2.1",
"pusher/pusher-php-server": "^7.2",
- "resend/resend-laravel": "^0.5.0",
- "sentry/sentry-laravel": "^3.4",
+ "sentry/sentry-laravel": "^4.6",
"socialiteproviders/microsoft-azure": "^5.1",
"spatie/laravel-activitylog": "^4.7.3",
"spatie/laravel-data": "^3.4.3",
@@ -43,14 +42,15 @@
"stripe/stripe-php": "^12.0",
"symfony/yaml": "^6.2",
"visus/cuid2": "^2.0.0",
- "yosymfony/toml": "^1.0"
+ "yosymfony/toml": "^1.0",
+ "zircote/swagger-php": "^4.10"
},
"require-dev": {
"fakerphp/faker": "^v1.21.0",
- "laravel/dusk": "^v7.7.0",
+ "laravel/dusk": "^v8.0",
"laravel/pint": "^1.16",
"mockery/mockery": "^1.5.1",
- "nunomaduro/collision": "^v7.4.0",
+ "nunomaduro/collision": "^v8.1",
"pestphp/pest": "^2.16",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10.0.19",
@@ -106,4 +106,4 @@
},
"minimum-stability": "stable",
"prefer-stable": true
-}
+}
\ No newline at end of file
diff --git a/composer.lock b/composer.lock
index 23d7d2e13..38ceda00f 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "168e351cec87acbea9c1c745b83eead2",
+ "content-hash": "c7c9cc002a9765c2395717c69ba8bfc6",
"packages": [
{
"name": "amphp/amp",
@@ -229,16 +229,16 @@
},
{
"name": "amphp/dns",
- "version": "v2.1.2",
+ "version": "v2.2.0",
"source": {
"type": "git",
"url": "https://github.com/amphp/dns.git",
- "reference": "04c88e67bef804203df934703bd422ea72f46b0e"
+ "reference": "758266b0ea7470e2e42cd098493bc6d6c7100cf7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/amphp/dns/zipball/04c88e67bef804203df934703bd422ea72f46b0e",
- "reference": "04c88e67bef804203df934703bd422ea72f46b0e",
+ "url": "https://api.github.com/repos/amphp/dns/zipball/758266b0ea7470e2e42cd098493bc6d6c7100cf7",
+ "reference": "758266b0ea7470e2e42cd098493bc6d6c7100cf7",
"shasum": ""
},
"require": {
@@ -305,7 +305,7 @@
],
"support": {
"issues": "https://github.com/amphp/dns/issues",
- "source": "https://github.com/amphp/dns/tree/v2.1.2"
+ "source": "https://github.com/amphp/dns/tree/v2.2.0"
},
"funding": [
{
@@ -313,7 +313,7 @@
"type": "github"
}
],
- "time": "2024-04-19T03:49:29+00:00"
+ "time": "2024-06-02T19:54:12+00:00"
},
{
"name": "amphp/parallel",
@@ -463,16 +463,16 @@
},
{
"name": "amphp/pipeline",
- "version": "v1.2.0",
+ "version": "v1.2.1",
"source": {
"type": "git",
"url": "https://github.com/amphp/pipeline.git",
- "reference": "f1c2ce35d27ae86ead018adb803eccca7421dd9b"
+ "reference": "66c095673aa5b6e689e63b52d19e577459129ab3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/amphp/pipeline/zipball/f1c2ce35d27ae86ead018adb803eccca7421dd9b",
- "reference": "f1c2ce35d27ae86ead018adb803eccca7421dd9b",
+ "url": "https://api.github.com/repos/amphp/pipeline/zipball/66c095673aa5b6e689e63b52d19e577459129ab3",
+ "reference": "66c095673aa5b6e689e63b52d19e577459129ab3",
"shasum": ""
},
"require": {
@@ -518,7 +518,7 @@
],
"support": {
"issues": "https://github.com/amphp/pipeline/issues",
- "source": "https://github.com/amphp/pipeline/tree/v1.2.0"
+ "source": "https://github.com/amphp/pipeline/tree/v1.2.1"
},
"funding": [
{
@@ -526,7 +526,7 @@
"type": "github"
}
],
- "time": "2024-03-10T14:48:16+00:00"
+ "time": "2024-07-04T00:56:47+00:00"
},
{
"name": "amphp/process",
@@ -867,16 +867,16 @@
},
{
"name": "aws/aws-crt-php",
- "version": "v1.2.5",
+ "version": "v1.2.6",
"source": {
"type": "git",
"url": "https://github.com/awslabs/aws-crt-php.git",
- "reference": "0ea1f04ec5aa9f049f97e012d1ed63b76834a31b"
+ "reference": "a63485b65b6b3367039306496d49737cf1995408"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/0ea1f04ec5aa9f049f97e012d1ed63b76834a31b",
- "reference": "0ea1f04ec5aa9f049f97e012d1ed63b76834a31b",
+ "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/a63485b65b6b3367039306496d49737cf1995408",
+ "reference": "a63485b65b6b3367039306496d49737cf1995408",
"shasum": ""
},
"require": {
@@ -915,22 +915,22 @@
],
"support": {
"issues": "https://github.com/awslabs/aws-crt-php/issues",
- "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.5"
+ "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.6"
},
- "time": "2024-04-19T21:30:56+00:00"
+ "time": "2024-06-13T17:21:28+00:00"
},
{
"name": "aws/aws-sdk-php",
- "version": "3.308.4",
+ "version": "3.316.1",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
- "reference": "c88e9df7e076b6e2c652a1c87d2c3af0a9ac30b6"
+ "reference": "888cee2adf890a5b749cc22c0f05051b53619d33"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/c88e9df7e076b6e2c652a1c87d2c3af0a9ac30b6",
- "reference": "c88e9df7e076b6e2c652a1c87d2c3af0a9ac30b6",
+ "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/888cee2adf890a5b749cc22c0f05051b53619d33",
+ "reference": "888cee2adf890a5b749cc22c0f05051b53619d33",
"shasum": ""
},
"require": {
@@ -1010,9 +1010,9 @@
"support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues",
- "source": "https://github.com/aws/aws-sdk-php/tree/3.308.4"
+ "source": "https://github.com/aws/aws-sdk-php/tree/3.316.1"
},
- "time": "2024-05-28T18:05:38+00:00"
+ "time": "2024-07-09T18:09:27+00:00"
},
{
"name": "bacon/bacon-qr-code",
@@ -1197,72 +1197,6 @@
],
"time": "2023-12-11T17:09:12+00:00"
},
- {
- "name": "clue/stream-filter",
- "version": "v1.7.0",
- "source": {
- "type": "git",
- "url": "https://github.com/clue/stream-filter.git",
- "reference": "049509fef80032cb3f051595029ab75b49a3c2f7"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/clue/stream-filter/zipball/049509fef80032cb3f051595029ab75b49a3c2f7",
- "reference": "049509fef80032cb3f051595029ab75b49a3c2f7",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3"
- },
- "require-dev": {
- "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36"
- },
- "type": "library",
- "autoload": {
- "files": [
- "src/functions_include.php"
- ],
- "psr-4": {
- "Clue\\StreamFilter\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Christian Lück",
- "email": "christian@clue.engineering"
- }
- ],
- "description": "A simple and modern approach to stream filtering in PHP",
- "homepage": "https://github.com/clue/stream-filter",
- "keywords": [
- "bucket brigade",
- "callback",
- "filter",
- "php_user_filter",
- "stream",
- "stream_filter_append",
- "stream_filter_register"
- ],
- "support": {
- "issues": "https://github.com/clue/stream-filter/issues",
- "source": "https://github.com/clue/stream-filter/tree/v1.7.0"
- },
- "funding": [
- {
- "url": "https://clue.engineering/support",
- "type": "custom"
- },
- {
- "url": "https://github.com/clue",
- "type": "github"
- }
- ],
- "time": "2023-12-20T15:40:13+00:00"
- },
{
"name": "danharrin/livewire-rate-limiting",
"version": "v1.3.1",
@@ -1413,16 +1347,16 @@
},
{
"name": "dflydev/dot-access-data",
- "version": "v3.0.2",
+ "version": "v3.0.3",
"source": {
"type": "git",
"url": "https://github.com/dflydev/dflydev-dot-access-data.git",
- "reference": "f41715465d65213d644d3141a6a93081be5d3549"
+ "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/f41715465d65213d644d3141a6a93081be5d3549",
- "reference": "f41715465d65213d644d3141a6a93081be5d3549",
+ "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f",
+ "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f",
"shasum": ""
},
"require": {
@@ -1482,9 +1416,9 @@
],
"support": {
"issues": "https://github.com/dflydev/dflydev-dot-access-data/issues",
- "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.2"
+ "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3"
},
- "time": "2022-10-27T11:44:00+00:00"
+ "time": "2024-07-08T12:26:09+00:00"
},
{
"name": "doctrine/cache",
@@ -1581,16 +1515,16 @@
},
{
"name": "doctrine/dbal",
- "version": "3.8.4",
+ "version": "3.8.6",
"source": {
"type": "git",
"url": "https://github.com/doctrine/dbal.git",
- "reference": "b05e48a745f722801f55408d0dbd8003b403dbbd"
+ "reference": "b7411825cf7efb7e51f9791dea19d86e43b399a1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/dbal/zipball/b05e48a745f722801f55408d0dbd8003b403dbbd",
- "reference": "b05e48a745f722801f55408d0dbd8003b403dbbd",
+ "url": "https://api.github.com/repos/doctrine/dbal/zipball/b7411825cf7efb7e51f9791dea19d86e43b399a1",
+ "reference": "b7411825cf7efb7e51f9791dea19d86e43b399a1",
"shasum": ""
},
"require": {
@@ -1606,12 +1540,12 @@
"doctrine/coding-standard": "12.0.0",
"fig/log-test": "^1",
"jetbrains/phpstorm-stubs": "2023.1",
- "phpstan/phpstan": "1.10.58",
- "phpstan/phpstan-strict-rules": "^1.5",
- "phpunit/phpunit": "9.6.16",
+ "phpstan/phpstan": "1.11.5",
+ "phpstan/phpstan-strict-rules": "^1.6",
+ "phpunit/phpunit": "9.6.19",
"psalm/plugin-phpunit": "0.18.4",
"slevomat/coding-standard": "8.13.1",
- "squizlabs/php_codesniffer": "3.9.0",
+ "squizlabs/php_codesniffer": "3.10.1",
"symfony/cache": "^5.4|^6.0|^7.0",
"symfony/console": "^4.4|^5.4|^6.0|^7.0",
"vimeo/psalm": "4.30.0"
@@ -1674,7 +1608,7 @@
],
"support": {
"issues": "https://github.com/doctrine/dbal/issues",
- "source": "https://github.com/doctrine/dbal/tree/3.8.4"
+ "source": "https://github.com/doctrine/dbal/tree/3.8.6"
},
"funding": [
{
@@ -1690,7 +1624,7 @@
"type": "tidelift"
}
],
- "time": "2024-04-25T07:04:44+00:00"
+ "time": "2024-06-19T10:38:17+00:00"
},
{
"name": "doctrine/deprecations",
@@ -2733,64 +2667,6 @@
],
"time": "2023-12-03T19:50:20+00:00"
},
- {
- "name": "http-interop/http-factory-guzzle",
- "version": "1.2.0",
- "source": {
- "type": "git",
- "url": "https://github.com/http-interop/http-factory-guzzle.git",
- "reference": "8f06e92b95405216b237521cc64c804dd44c4a81"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/http-interop/http-factory-guzzle/zipball/8f06e92b95405216b237521cc64c804dd44c4a81",
- "reference": "8f06e92b95405216b237521cc64c804dd44c4a81",
- "shasum": ""
- },
- "require": {
- "guzzlehttp/psr7": "^1.7||^2.0",
- "php": ">=7.3",
- "psr/http-factory": "^1.0"
- },
- "provide": {
- "psr/http-factory-implementation": "^1.0"
- },
- "require-dev": {
- "http-interop/http-factory-tests": "^0.9",
- "phpunit/phpunit": "^9.5"
- },
- "suggest": {
- "guzzlehttp/psr7": "Includes an HTTP factory starting in version 2.0"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "Http\\Factory\\Guzzle\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "PHP-FIG",
- "homepage": "http://www.php-fig.org/"
- }
- ],
- "description": "An HTTP Factory using Guzzle PSR7",
- "keywords": [
- "factory",
- "http",
- "psr-17",
- "psr-7"
- ],
- "support": {
- "issues": "https://github.com/http-interop/http-factory-guzzle/issues",
- "source": "https://github.com/http-interop/http-factory-guzzle/tree/1.2.0"
- },
- "time": "2021-07-21T13:50:14+00:00"
- },
{
"name": "jean85/pretty-package-versions",
"version": "2.0.6",
@@ -2910,16 +2786,16 @@
},
{
"name": "laravel/fortify",
- "version": "v1.21.3",
+ "version": "v1.21.5",
"source": {
"type": "git",
"url": "https://github.com/laravel/fortify.git",
- "reference": "a725684d17959c4750f3b441ff2e94ecde7793a1"
+ "reference": "3eaf01ec826c4f653628202640a4450784f78b15"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/fortify/zipball/a725684d17959c4750f3b441ff2e94ecde7793a1",
- "reference": "a725684d17959c4750f3b441ff2e94ecde7793a1",
+ "url": "https://api.github.com/repos/laravel/fortify/zipball/3eaf01ec826c4f653628202640a4450784f78b15",
+ "reference": "3eaf01ec826c4f653628202640a4450784f78b15",
"shasum": ""
},
"require": {
@@ -2971,20 +2847,20 @@
"issues": "https://github.com/laravel/fortify/issues",
"source": "https://github.com/laravel/fortify"
},
- "time": "2024-05-08T18:07:38+00:00"
+ "time": "2024-07-04T14:36:27+00:00"
},
{
"name": "laravel/framework",
- "version": "v10.48.12",
+ "version": "v11.15.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
- "reference": "590afea38e708022662629fbf5184351fa82cf08"
+ "reference": "ba85f1c019bed59b3c736c9c4502805efd0ba84b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/framework/zipball/590afea38e708022662629fbf5184351fa82cf08",
- "reference": "590afea38e708022662629fbf5184351fa82cf08",
+ "url": "https://api.github.com/repos/laravel/framework/zipball/ba85f1c019bed59b3c736c9c4502805efd0ba84b",
+ "reference": "ba85f1c019bed59b3c736c9c4502805efd0ba84b",
"shasum": ""
},
"require": {
@@ -3000,44 +2876,44 @@
"ext-openssl": "*",
"ext-session": "*",
"ext-tokenizer": "*",
- "fruitcake/php-cors": "^1.2",
+ "fruitcake/php-cors": "^1.3",
+ "guzzlehttp/guzzle": "^7.8",
"guzzlehttp/uri-template": "^1.0",
- "laravel/prompts": "^0.1.9",
+ "laravel/prompts": "^0.1.18",
"laravel/serializable-closure": "^1.3",
"league/commonmark": "^2.2.1",
"league/flysystem": "^3.8.0",
"monolog/monolog": "^3.0",
- "nesbot/carbon": "^2.67",
- "nunomaduro/termwind": "^1.13",
- "php": "^8.1",
+ "nesbot/carbon": "^2.72.2|^3.0",
+ "nunomaduro/termwind": "^2.0",
+ "php": "^8.2",
"psr/container": "^1.1.1|^2.0.1",
"psr/log": "^1.0|^2.0|^3.0",
"psr/simple-cache": "^1.0|^2.0|^3.0",
"ramsey/uuid": "^4.7",
- "symfony/console": "^6.2",
- "symfony/error-handler": "^6.2",
- "symfony/finder": "^6.2",
- "symfony/http-foundation": "^6.4",
- "symfony/http-kernel": "^6.2",
- "symfony/mailer": "^6.2",
- "symfony/mime": "^6.2",
- "symfony/process": "^6.2",
- "symfony/routing": "^6.2",
- "symfony/uid": "^6.2",
- "symfony/var-dumper": "^6.2",
+ "symfony/console": "^7.0",
+ "symfony/error-handler": "^7.0",
+ "symfony/finder": "^7.0",
+ "symfony/http-foundation": "^7.0",
+ "symfony/http-kernel": "^7.0",
+ "symfony/mailer": "^7.0",
+ "symfony/mime": "^7.0",
+ "symfony/polyfill-php83": "^1.28",
+ "symfony/process": "^7.0",
+ "symfony/routing": "^7.0",
+ "symfony/uid": "^7.0",
+ "symfony/var-dumper": "^7.0",
"tijsverkoyen/css-to-inline-styles": "^2.2.5",
"vlucas/phpdotenv": "^5.4.1",
"voku/portable-ascii": "^2.0"
},
"conflict": {
- "carbonphp/carbon-doctrine-types": ">=3.0",
- "doctrine/dbal": ">=4.0",
"mockery/mockery": "1.6.8",
- "phpunit/phpunit": ">=11.0.0",
"tightenco/collect": "<5.5.33"
},
"provide": {
"psr/container-implementation": "1.1|2.0",
+ "psr/log-implementation": "1.0|2.0|3.0",
"psr/simple-cache-implementation": "1.0|2.0|3.0"
},
"replace": {
@@ -3073,36 +2949,35 @@
"illuminate/testing": "self.version",
"illuminate/translation": "self.version",
"illuminate/validation": "self.version",
- "illuminate/view": "self.version"
+ "illuminate/view": "self.version",
+ "spatie/once": "*"
},
"require-dev": {
"ably/ably-php": "^1.0",
"aws/aws-sdk-php": "^3.235.5",
- "doctrine/dbal": "^3.5.1",
"ext-gmp": "*",
- "fakerphp/faker": "^1.21",
- "guzzlehttp/guzzle": "^7.5",
+ "fakerphp/faker": "^1.23",
"league/flysystem-aws-s3-v3": "^3.0",
"league/flysystem-ftp": "^3.0",
"league/flysystem-path-prefixing": "^3.3",
"league/flysystem-read-only": "^3.3",
"league/flysystem-sftp-v3": "^3.0",
- "mockery/mockery": "^1.5.1",
+ "mockery/mockery": "^1.6",
"nyholm/psr7": "^1.2",
- "orchestra/testbench-core": "^8.23.4",
- "pda/pheanstalk": "^4.0",
- "phpstan/phpstan": "^1.4.7",
- "phpunit/phpunit": "^10.0.7",
+ "orchestra/testbench-core": "^9.1.5",
+ "pda/pheanstalk": "^5.0",
+ "phpstan/phpstan": "^1.11.5",
+ "phpunit/phpunit": "^10.5|^11.0",
"predis/predis": "^2.0.2",
- "symfony/cache": "^6.2",
- "symfony/http-client": "^6.2.4",
- "symfony/psr-http-message-bridge": "^2.0"
+ "resend/resend-php": "^0.10.0",
+ "symfony/cache": "^7.0",
+ "symfony/http-client": "^7.0",
+ "symfony/psr-http-message-bridge": "^7.0"
},
"suggest": {
"ably/ably-php": "Required to use the Ably broadcast driver (^1.0).",
"aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.235.5).",
- "brianium/paratest": "Required to run tests in parallel (^6.0).",
- "doctrine/dbal": "Required to rename columns and drop SQLite columns (^3.5.1).",
+ "brianium/paratest": "Required to run tests in parallel (^7.0|^8.0).",
"ext-apcu": "Required to use the APC cache driver.",
"ext-fileinfo": "Required to use the Filesystem class.",
"ext-ftp": "Required to use the Flysystem FTP driver.",
@@ -3111,34 +2986,34 @@
"ext-pcntl": "Required to use all features of the queue worker and console signal trapping.",
"ext-pdo": "Required to use all database features.",
"ext-posix": "Required to use all features of the queue worker.",
- "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).",
+ "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0|^6.0).",
"fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).",
"filp/whoops": "Required for friendly error pages in development (^2.14.3).",
- "guzzlehttp/guzzle": "Required to use the HTTP Client and the ping methods on schedules (^7.5).",
"laravel/tinker": "Required to use the tinker console command (^2.0).",
"league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.0).",
"league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.0).",
"league/flysystem-path-prefixing": "Required to use the scoped driver (^3.3).",
"league/flysystem-read-only": "Required to use read-only disks (^3.3)",
"league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.0).",
- "mockery/mockery": "Required to use mocking (^1.5.1).",
+ "mockery/mockery": "Required to use mocking (^1.6).",
"nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).",
- "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).",
- "phpunit/phpunit": "Required to use assertions and run tests (^9.5.8|^10.0.7).",
+ "pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).",
+ "phpunit/phpunit": "Required to use assertions and run tests (^10.5|^11.0).",
"predis/predis": "Required to use the predis connector (^2.0.2).",
"psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).",
"pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).",
- "symfony/cache": "Required to PSR-6 cache bridge (^6.2).",
- "symfony/filesystem": "Required to enable support for relative symbolic links (^6.2).",
- "symfony/http-client": "Required to enable support for the Symfony API mail transports (^6.2).",
- "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^6.2).",
- "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^6.2).",
- "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0)."
+ "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).",
+ "symfony/cache": "Required to PSR-6 cache bridge (^7.0).",
+ "symfony/filesystem": "Required to enable support for relative symbolic links (^7.0).",
+ "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.0).",
+ "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.0).",
+ "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.0).",
+ "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.0)."
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "10.x-dev"
+ "dev-master": "11.x-dev"
}
},
"autoload": {
@@ -3178,20 +3053,20 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
- "time": "2024-05-28T15:46:19+00:00"
+ "time": "2024-07-09T15:38:12+00:00"
},
{
"name": "laravel/horizon",
- "version": "v5.24.4",
+ "version": "v5.25.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/horizon.git",
- "reference": "8d31ff178bf5493efc2b2629c10612054f31f584"
+ "reference": "81e62cee5b3feaf169d683b8890e33bf454698ab"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/horizon/zipball/8d31ff178bf5493efc2b2629c10612054f31f584",
- "reference": "8d31ff178bf5493efc2b2629c10612054f31f584",
+ "url": "https://api.github.com/repos/laravel/horizon/zipball/81e62cee5b3feaf169d683b8890e33bf454698ab",
+ "reference": "81e62cee5b3feaf169d683b8890e33bf454698ab",
"shasum": ""
},
"require": {
@@ -3255,22 +3130,22 @@
],
"support": {
"issues": "https://github.com/laravel/horizon/issues",
- "source": "https://github.com/laravel/horizon/tree/v5.24.4"
+ "source": "https://github.com/laravel/horizon/tree/v5.25.0"
},
- "time": "2024-05-03T13:34:14+00:00"
+ "time": "2024-07-05T16:46:31+00:00"
},
{
"name": "laravel/prompts",
- "version": "v0.1.23",
+ "version": "v0.1.24",
"source": {
"type": "git",
"url": "https://github.com/laravel/prompts.git",
- "reference": "9bc4df7c699b0452c6b815e64a2d84b6d7f99400"
+ "reference": "409b0b4305273472f3754826e68f4edbd0150149"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/prompts/zipball/9bc4df7c699b0452c6b815e64a2d84b6d7f99400",
- "reference": "9bc4df7c699b0452c6b815e64a2d84b6d7f99400",
+ "url": "https://api.github.com/repos/laravel/prompts/zipball/409b0b4305273472f3754826e68f4edbd0150149",
+ "reference": "409b0b4305273472f3754826e68f4edbd0150149",
"shasum": ""
},
"require": {
@@ -3313,43 +3188,41 @@
"description": "Add beautiful and user-friendly forms to your command-line applications.",
"support": {
"issues": "https://github.com/laravel/prompts/issues",
- "source": "https://github.com/laravel/prompts/tree/v0.1.23"
+ "source": "https://github.com/laravel/prompts/tree/v0.1.24"
},
- "time": "2024-05-27T13:53:20+00:00"
+ "time": "2024-06-17T13:58:22+00:00"
},
{
"name": "laravel/sanctum",
- "version": "v3.3.3",
+ "version": "v4.0.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/sanctum.git",
- "reference": "8c104366459739f3ada0e994bcd3e6fd681ce3d5"
+ "reference": "9cfc0ce80cabad5334efff73ec856339e8ec1ac1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/sanctum/zipball/8c104366459739f3ada0e994bcd3e6fd681ce3d5",
- "reference": "8c104366459739f3ada0e994bcd3e6fd681ce3d5",
+ "url": "https://api.github.com/repos/laravel/sanctum/zipball/9cfc0ce80cabad5334efff73ec856339e8ec1ac1",
+ "reference": "9cfc0ce80cabad5334efff73ec856339e8ec1ac1",
"shasum": ""
},
"require": {
"ext-json": "*",
- "illuminate/console": "^9.21|^10.0",
- "illuminate/contracts": "^9.21|^10.0",
- "illuminate/database": "^9.21|^10.0",
- "illuminate/support": "^9.21|^10.0",
- "php": "^8.0.2"
+ "illuminate/console": "^11.0",
+ "illuminate/contracts": "^11.0",
+ "illuminate/database": "^11.0",
+ "illuminate/support": "^11.0",
+ "php": "^8.2",
+ "symfony/console": "^7.0"
},
"require-dev": {
- "mockery/mockery": "^1.0",
- "orchestra/testbench": "^7.28.2|^8.8.3",
+ "mockery/mockery": "^1.6",
+ "orchestra/testbench": "^9.0",
"phpstan/phpstan": "^1.10",
- "phpunit/phpunit": "^9.6"
+ "phpunit/phpunit": "^10.5"
},
"type": "library",
"extra": {
- "branch-alias": {
- "dev-master": "3.x-dev"
- },
"laravel": {
"providers": [
"Laravel\\Sanctum\\SanctumServiceProvider"
@@ -3381,7 +3254,7 @@
"issues": "https://github.com/laravel/sanctum/issues",
"source": "https://github.com/laravel/sanctum"
},
- "time": "2023-12-19T18:44:48+00:00"
+ "time": "2024-04-10T19:39:58+00:00"
},
{
"name": "laravel/serializable-closure",
@@ -3445,16 +3318,16 @@
},
{
"name": "laravel/socialite",
- "version": "v5.14.0",
+ "version": "v5.15.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/socialite.git",
- "reference": "c7b0193a3753a29aff8ce80aa2f511917e6ed68a"
+ "reference": "cc02625f0bd1f95dc3688eb041cce0f1e709d029"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/socialite/zipball/c7b0193a3753a29aff8ce80aa2f511917e6ed68a",
- "reference": "c7b0193a3753a29aff8ce80aa2f511917e6ed68a",
+ "url": "https://api.github.com/repos/laravel/socialite/zipball/cc02625f0bd1f95dc3688eb041cce0f1e709d029",
+ "reference": "cc02625f0bd1f95dc3688eb041cce0f1e709d029",
"shasum": ""
},
"require": {
@@ -3513,7 +3386,7 @@
"issues": "https://github.com/laravel/socialite/issues",
"source": "https://github.com/laravel/socialite"
},
- "time": "2024-05-03T20:31:38+00:00"
+ "time": "2024-06-28T20:09:34+00:00"
},
{
"name": "laravel/tinker",
@@ -4733,16 +4606,16 @@
},
{
"name": "monolog/monolog",
- "version": "3.6.0",
+ "version": "3.7.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
- "reference": "4b18b21a5527a3d5ffdac2fd35d3ab25a9597654"
+ "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Seldaek/monolog/zipball/4b18b21a5527a3d5ffdac2fd35d3ab25a9597654",
- "reference": "4b18b21a5527a3d5ffdac2fd35d3ab25a9597654",
+ "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f4393b648b78a5408747de94fca38beb5f7e9ef8",
+ "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8",
"shasum": ""
},
"require": {
@@ -4818,7 +4691,7 @@
],
"support": {
"issues": "https://github.com/Seldaek/monolog/issues",
- "source": "https://github.com/Seldaek/monolog/tree/3.6.0"
+ "source": "https://github.com/Seldaek/monolog/tree/3.7.0"
},
"funding": [
{
@@ -4830,7 +4703,7 @@
"type": "tidelift"
}
],
- "time": "2024-04-12T21:02:21+00:00"
+ "time": "2024-06-28T09:40:51+00:00"
},
{
"name": "mtdowling/jmespath.php",
@@ -4900,42 +4773,41 @@
},
{
"name": "nesbot/carbon",
- "version": "2.72.3",
+ "version": "3.6.0",
"source": {
"type": "git",
"url": "https://github.com/briannesbitt/Carbon.git",
- "reference": "0c6fd108360c562f6e4fd1dedb8233b423e91c83"
+ "reference": "39c8ef752db6865717cc3fba63970c16f057982c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/0c6fd108360c562f6e4fd1dedb8233b423e91c83",
- "reference": "0c6fd108360c562f6e4fd1dedb8233b423e91c83",
+ "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/39c8ef752db6865717cc3fba63970c16f057982c",
+ "reference": "39c8ef752db6865717cc3fba63970c16f057982c",
"shasum": ""
},
"require": {
"carbonphp/carbon-doctrine-types": "*",
"ext-json": "*",
- "php": "^7.1.8 || ^8.0",
+ "php": "^8.1",
"psr/clock": "^1.0",
+ "symfony/clock": "^6.3 || ^7.0",
"symfony/polyfill-mbstring": "^1.0",
- "symfony/polyfill-php80": "^1.16",
- "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0"
+ "symfony/translation": "^4.4.18 || ^5.2.1|| ^6.0 || ^7.0"
},
"provide": {
"psr/clock-implementation": "1.0"
},
"require-dev": {
- "doctrine/dbal": "^2.0 || ^3.1.4 || ^4.0",
- "doctrine/orm": "^2.7 || ^3.0",
- "friendsofphp/php-cs-fixer": "^3.0",
- "kylekatarnls/multi-tester": "^2.0",
- "ondrejmirtes/better-reflection": "*",
- "phpmd/phpmd": "^2.9",
- "phpstan/extension-installer": "^1.0",
- "phpstan/phpstan": "^0.12.99 || ^1.7.14",
- "phpunit/php-file-iterator": "^2.0.5 || ^3.0.6",
- "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20",
- "squizlabs/php_codesniffer": "^3.4"
+ "doctrine/dbal": "^3.6.3 || ^4.0",
+ "doctrine/orm": "^2.15.2 || ^3.0",
+ "friendsofphp/php-cs-fixer": "^3.57.2",
+ "kylekatarnls/multi-tester": "^2.5.3",
+ "ondrejmirtes/better-reflection": "^6.25.0.4",
+ "phpmd/phpmd": "^2.15.0",
+ "phpstan/extension-installer": "^1.3.1",
+ "phpstan/phpstan": "^1.11.2",
+ "phpunit/phpunit": "^10.5.20",
+ "squizlabs/php_codesniffer": "^3.9.0"
},
"bin": [
"bin/carbon"
@@ -4943,8 +4815,8 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-3.x": "3.x-dev",
- "dev-master": "2.x-dev"
+ "dev-master": "3.x-dev",
+ "dev-2.x": "2.x-dev"
},
"laravel": {
"providers": [
@@ -5003,7 +4875,7 @@
"type": "tidelift"
}
],
- "time": "2024-01-25T10:35:09+00:00"
+ "time": "2024-06-20T15:52:59+00:00"
},
{
"name": "nette/schema",
@@ -5155,16 +5027,16 @@
},
{
"name": "nikic/php-parser",
- "version": "v5.0.2",
+ "version": "v5.1.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13"
+ "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13",
- "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1",
+ "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1",
"shasum": ""
},
"require": {
@@ -5175,7 +5047,7 @@
},
"require-dev": {
"ircmaxell/php-yacc": "^0.0.7",
- "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
+ "phpunit/phpunit": "^9.0"
},
"bin": [
"bin/php-parse"
@@ -5207,9 +5079,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0"
},
- "time": "2024-03-05T20:51:40+00:00"
+ "time": "2024-07-01T20:03:41+00:00"
},
{
"name": "nubs/random-name-generator",
@@ -5266,33 +5138,32 @@
},
{
"name": "nunomaduro/termwind",
- "version": "v1.15.1",
+ "version": "v2.0.1",
"source": {
"type": "git",
"url": "https://github.com/nunomaduro/termwind.git",
- "reference": "8ab0b32c8caa4a2e09700ea32925441385e4a5dc"
+ "reference": "58c4c58cf23df7f498daeb97092e34f5259feb6a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/8ab0b32c8caa4a2e09700ea32925441385e4a5dc",
- "reference": "8ab0b32c8caa4a2e09700ea32925441385e4a5dc",
+ "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/58c4c58cf23df7f498daeb97092e34f5259feb6a",
+ "reference": "58c4c58cf23df7f498daeb97092e34f5259feb6a",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
- "php": "^8.0",
- "symfony/console": "^5.3.0|^6.0.0"
+ "php": "^8.2",
+ "symfony/console": "^7.0.4"
},
"require-dev": {
- "ergebnis/phpstan-rules": "^1.0.",
- "illuminate/console": "^8.0|^9.0",
- "illuminate/support": "^8.0|^9.0",
- "laravel/pint": "^1.0.0",
- "pestphp/pest": "^1.21.0",
- "pestphp/pest-plugin-mock": "^1.0",
- "phpstan/phpstan": "^1.4.6",
- "phpstan/phpstan-strict-rules": "^1.1.0",
- "symfony/var-dumper": "^5.2.7|^6.0.0",
+ "ergebnis/phpstan-rules": "^2.2.0",
+ "illuminate/console": "^11.0.0",
+ "laravel/pint": "^1.14.0",
+ "mockery/mockery": "^1.6.7",
+ "pestphp/pest": "^2.34.1",
+ "phpstan/phpstan": "^1.10.59",
+ "phpstan/phpstan-strict-rules": "^1.5.2",
+ "symfony/var-dumper": "^7.0.4",
"thecodingmachine/phpstan-strict-rules": "^1.0.0"
},
"type": "library",
@@ -5301,6 +5172,9 @@
"providers": [
"Termwind\\Laravel\\TermwindServiceProvider"
]
+ },
+ "branch-alias": {
+ "dev-2.x": "2.x-dev"
}
},
"autoload": {
@@ -5332,7 +5206,7 @@
],
"support": {
"issues": "https://github.com/nunomaduro/termwind/issues",
- "source": "https://github.com/nunomaduro/termwind/tree/v1.15.1"
+ "source": "https://github.com/nunomaduro/termwind/tree/v2.0.1"
},
"funding": [
{
@@ -5348,7 +5222,7 @@
"type": "github"
}
],
- "time": "2023-02-08T01:06:31+00:00"
+ "time": "2024-03-06T16:17:14+00:00"
},
{
"name": "nyholm/psr7",
@@ -5632,385 +5506,132 @@
"time": "2024-04-22T22:05:04+00:00"
},
{
- "name": "php-http/client-common",
- "version": "2.7.1",
+ "name": "php-di/invoker",
+ "version": "2.3.4",
"source": {
"type": "git",
- "url": "https://github.com/php-http/client-common.git",
- "reference": "1e19c059b0e4d5f717bf5d524d616165aeab0612"
+ "url": "https://github.com/PHP-DI/Invoker.git",
+ "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-http/client-common/zipball/1e19c059b0e4d5f717bf5d524d616165aeab0612",
- "reference": "1e19c059b0e4d5f717bf5d524d616165aeab0612",
+ "url": "https://api.github.com/repos/PHP-DI/Invoker/zipball/33234b32dafa8eb69202f950a1fc92055ed76a86",
+ "reference": "33234b32dafa8eb69202f950a1fc92055ed76a86",
"shasum": ""
},
"require": {
- "php": "^7.1 || ^8.0",
- "php-http/httplug": "^2.0",
- "php-http/message": "^1.6",
- "psr/http-client": "^1.0",
- "psr/http-factory": "^1.0",
- "psr/http-message": "^1.0 || ^2.0",
- "symfony/options-resolver": "~4.0.15 || ~4.1.9 || ^4.2.1 || ^5.0 || ^6.0 || ^7.0",
- "symfony/polyfill-php80": "^1.17"
+ "php": ">=7.3",
+ "psr/container": "^1.0|^2.0"
},
"require-dev": {
- "doctrine/instantiator": "^1.1",
- "guzzlehttp/psr7": "^1.4",
- "nyholm/psr7": "^1.2",
- "phpspec/phpspec": "^5.1 || ^6.3 || ^7.1",
- "phpspec/prophecy": "^1.10.2",
- "phpunit/phpunit": "^7.5.20 || ^8.5.33 || ^9.6.7"
- },
- "suggest": {
- "ext-json": "To detect JSON responses with the ContentTypePlugin",
- "ext-libxml": "To detect XML responses with the ContentTypePlugin",
- "php-http/cache-plugin": "PSR-6 Cache plugin",
- "php-http/logger-plugin": "PSR-3 Logger plugin",
- "php-http/stopwatch-plugin": "Symfony Stopwatch plugin"
+ "athletic/athletic": "~0.1.8",
+ "mnapoli/hard-mode": "~0.3.0",
+ "phpunit/phpunit": "^9.0"
},
"type": "library",
"autoload": {
"psr-4": {
- "Http\\Client\\Common\\": "src/"
+ "Invoker\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
- "authors": [
- {
- "name": "Márk Sági-Kazár",
- "email": "mark.sagikazar@gmail.com"
- }
- ],
- "description": "Common HTTP Client implementations and tools for HTTPlug",
- "homepage": "http://httplug.io",
+ "description": "Generic and extensible callable invoker",
+ "homepage": "https://github.com/PHP-DI/Invoker",
"keywords": [
- "client",
- "common",
- "http",
- "httplug"
+ "callable",
+ "dependency",
+ "dependency-injection",
+ "injection",
+ "invoke",
+ "invoker"
],
"support": {
- "issues": "https://github.com/php-http/client-common/issues",
- "source": "https://github.com/php-http/client-common/tree/2.7.1"
+ "issues": "https://github.com/PHP-DI/Invoker/issues",
+ "source": "https://github.com/PHP-DI/Invoker/tree/2.3.4"
},
- "time": "2023-11-30T10:31:25+00:00"
+ "funding": [
+ {
+ "url": "https://github.com/mnapoli",
+ "type": "github"
+ }
+ ],
+ "time": "2023-09-08T09:24:21+00:00"
},
{
- "name": "php-http/discovery",
- "version": "1.19.4",
+ "name": "php-di/php-di",
+ "version": "7.0.6",
"source": {
"type": "git",
- "url": "https://github.com/php-http/discovery.git",
- "reference": "0700efda8d7526335132360167315fdab3aeb599"
+ "url": "https://github.com/PHP-DI/PHP-DI.git",
+ "reference": "8097948a89f6ec782839b3e958432f427cac37fd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-http/discovery/zipball/0700efda8d7526335132360167315fdab3aeb599",
- "reference": "0700efda8d7526335132360167315fdab3aeb599",
+ "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/8097948a89f6ec782839b3e958432f427cac37fd",
+ "reference": "8097948a89f6ec782839b3e958432f427cac37fd",
"shasum": ""
},
"require": {
- "composer-plugin-api": "^1.0|^2.0",
- "php": "^7.1 || ^8.0"
- },
- "conflict": {
- "nyholm/psr7": "<1.0",
- "zendframework/zend-diactoros": "*"
+ "laravel/serializable-closure": "^1.0",
+ "php": ">=8.0",
+ "php-di/invoker": "^2.0",
+ "psr/container": "^1.1 || ^2.0"
},
"provide": {
- "php-http/async-client-implementation": "*",
- "php-http/client-implementation": "*",
- "psr/http-client-implementation": "*",
- "psr/http-factory-implementation": "*",
- "psr/http-message-implementation": "*"
+ "psr/container-implementation": "^1.0"
},
"require-dev": {
- "composer/composer": "^1.0.2|^2.0",
- "graham-campbell/phpspec-skip-example-extension": "^5.0",
- "php-http/httplug": "^1.0 || ^2.0",
- "php-http/message-factory": "^1.0",
- "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3",
- "sebastian/comparator": "^3.0.5 || ^4.0.8",
- "symfony/phpunit-bridge": "^6.4.4 || ^7.0.1"
- },
- "type": "composer-plugin",
- "extra": {
- "class": "Http\\Discovery\\Composer\\Plugin",
- "plugin-optional": true
- },
- "autoload": {
- "psr-4": {
- "Http\\Discovery\\": "src/"
- },
- "exclude-from-classmap": [
- "src/Composer/Plugin.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Márk Sági-Kazár",
- "email": "mark.sagikazar@gmail.com"
- }
- ],
- "description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations",
- "homepage": "http://php-http.org",
- "keywords": [
- "adapter",
- "client",
- "discovery",
- "factory",
- "http",
- "message",
- "psr17",
- "psr7"
- ],
- "support": {
- "issues": "https://github.com/php-http/discovery/issues",
- "source": "https://github.com/php-http/discovery/tree/1.19.4"
- },
- "time": "2024-03-29T13:00:05+00:00"
- },
- {
- "name": "php-http/httplug",
- "version": "2.4.0",
- "source": {
- "type": "git",
- "url": "https://github.com/php-http/httplug.git",
- "reference": "625ad742c360c8ac580fcc647a1541d29e257f67"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/php-http/httplug/zipball/625ad742c360c8ac580fcc647a1541d29e257f67",
- "reference": "625ad742c360c8ac580fcc647a1541d29e257f67",
- "shasum": ""
- },
- "require": {
- "php": "^7.1 || ^8.0",
- "php-http/promise": "^1.1",
- "psr/http-client": "^1.0",
- "psr/http-message": "^1.0 || ^2.0"
- },
- "require-dev": {
- "friends-of-phpspec/phpspec-code-coverage": "^4.1 || ^5.0 || ^6.0",
- "phpspec/phpspec": "^5.1 || ^6.0 || ^7.0"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "Http\\Client\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Eric GELOEN",
- "email": "geloen.eric@gmail.com"
- },
- {
- "name": "Márk Sági-Kazár",
- "email": "mark.sagikazar@gmail.com",
- "homepage": "https://sagikazarmark.hu"
- }
- ],
- "description": "HTTPlug, the HTTP client abstraction for PHP",
- "homepage": "http://httplug.io",
- "keywords": [
- "client",
- "http"
- ],
- "support": {
- "issues": "https://github.com/php-http/httplug/issues",
- "source": "https://github.com/php-http/httplug/tree/2.4.0"
- },
- "time": "2023-04-14T15:10:03+00:00"
- },
- {
- "name": "php-http/message",
- "version": "1.16.1",
- "source": {
- "type": "git",
- "url": "https://github.com/php-http/message.git",
- "reference": "5997f3289332c699fa2545c427826272498a2088"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/php-http/message/zipball/5997f3289332c699fa2545c427826272498a2088",
- "reference": "5997f3289332c699fa2545c427826272498a2088",
- "shasum": ""
- },
- "require": {
- "clue/stream-filter": "^1.5",
- "php": "^7.2 || ^8.0",
- "psr/http-message": "^1.1 || ^2.0"
- },
- "provide": {
- "php-http/message-factory-implementation": "1.0"
- },
- "require-dev": {
- "ergebnis/composer-normalize": "^2.6",
- "ext-zlib": "*",
- "guzzlehttp/psr7": "^1.0 || ^2.0",
- "laminas/laminas-diactoros": "^2.0 || ^3.0",
- "php-http/message-factory": "^1.0.2",
- "phpspec/phpspec": "^5.1 || ^6.3 || ^7.1",
- "slim/slim": "^3.0"
+ "friendsofphp/php-cs-fixer": "^3",
+ "friendsofphp/proxy-manager-lts": "^1",
+ "mnapoli/phpunit-easymock": "^1.3",
+ "phpunit/phpunit": "^9.5",
+ "vimeo/psalm": "^4.6"
},
"suggest": {
- "ext-zlib": "Used with compressor/decompressor streams",
- "guzzlehttp/psr7": "Used with Guzzle PSR-7 Factories",
- "laminas/laminas-diactoros": "Used with Diactoros Factories",
- "slim/slim": "Used with Slim Framework PSR-7 implementation"
+ "friendsofphp/proxy-manager-lts": "Install it if you want to use lazy injection (version ^1)"
},
"type": "library",
"autoload": {
"files": [
- "src/filters.php"
+ "src/functions.php"
],
"psr-4": {
- "Http\\Message\\": "src/"
+ "DI\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
- "authors": [
- {
- "name": "Márk Sági-Kazár",
- "email": "mark.sagikazar@gmail.com"
- }
- ],
- "description": "HTTP Message related tools",
- "homepage": "http://php-http.org",
+ "description": "The dependency injection container for humans",
+ "homepage": "https://php-di.org/",
"keywords": [
- "http",
- "message",
- "psr-7"
+ "PSR-11",
+ "container",
+ "container-interop",
+ "dependency injection",
+ "di",
+ "ioc",
+ "psr11"
],
"support": {
- "issues": "https://github.com/php-http/message/issues",
- "source": "https://github.com/php-http/message/tree/1.16.1"
+ "issues": "https://github.com/PHP-DI/PHP-DI/issues",
+ "source": "https://github.com/PHP-DI/PHP-DI/tree/7.0.6"
},
- "time": "2024-03-07T13:22:09+00:00"
- },
- {
- "name": "php-http/message-factory",
- "version": "1.1.0",
- "source": {
- "type": "git",
- "url": "https://github.com/php-http/message-factory.git",
- "reference": "4d8778e1c7d405cbb471574821c1ff5b68cc8f57"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/php-http/message-factory/zipball/4d8778e1c7d405cbb471574821c1ff5b68cc8f57",
- "reference": "4d8778e1c7d405cbb471574821c1ff5b68cc8f57",
- "shasum": ""
- },
- "require": {
- "php": ">=5.4",
- "psr/http-message": "^1.0 || ^2.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Http\\Message\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
+ "funding": [
{
- "name": "Márk Sági-Kazár",
- "email": "mark.sagikazar@gmail.com"
- }
- ],
- "description": "Factory interfaces for PSR-7 HTTP Message",
- "homepage": "http://php-http.org",
- "keywords": [
- "factory",
- "http",
- "message",
- "stream",
- "uri"
- ],
- "support": {
- "issues": "https://github.com/php-http/message-factory/issues",
- "source": "https://github.com/php-http/message-factory/tree/1.1.0"
- },
- "abandoned": "psr/http-factory",
- "time": "2023-04-14T14:16:17+00:00"
- },
- {
- "name": "php-http/promise",
- "version": "1.3.1",
- "source": {
- "type": "git",
- "url": "https://github.com/php-http/promise.git",
- "reference": "fc85b1fba37c169a69a07ef0d5a8075770cc1f83"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/php-http/promise/zipball/fc85b1fba37c169a69a07ef0d5a8075770cc1f83",
- "reference": "fc85b1fba37c169a69a07ef0d5a8075770cc1f83",
- "shasum": ""
- },
- "require": {
- "php": "^7.1 || ^8.0"
- },
- "require-dev": {
- "friends-of-phpspec/phpspec-code-coverage": "^4.3.2 || ^6.3",
- "phpspec/phpspec": "^5.1.2 || ^6.2 || ^7.4"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "Http\\Promise\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Joel Wurtz",
- "email": "joel.wurtz@gmail.com"
+ "url": "https://github.com/mnapoli",
+ "type": "github"
},
{
- "name": "Márk Sági-Kazár",
- "email": "mark.sagikazar@gmail.com"
+ "url": "https://tidelift.com/funding/github/packagist/php-di/php-di",
+ "type": "tidelift"
}
],
- "description": "Promise used for asynchronous HTTP requests",
- "homepage": "http://httplug.io",
- "keywords": [
- "promise"
- ],
- "support": {
- "issues": "https://github.com/php-http/promise/issues",
- "source": "https://github.com/php-http/promise/tree/1.3.1"
- },
- "time": "2024-03-15T13:55:21+00:00"
+ "time": "2023-11-02T10:04:50+00:00"
},
{
"name": "phpdocumentor/reflection-common",
@@ -6200,20 +5821,20 @@
},
{
"name": "phpseclib/phpseclib",
- "version": "3.0.37",
+ "version": "3.0.39",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
- "reference": "cfa2013d0f68c062055180dd4328cc8b9d1f30b8"
+ "reference": "211ebc399c6e73c225a018435fe5ae209d1d1485"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/cfa2013d0f68c062055180dd4328cc8b9d1f30b8",
- "reference": "cfa2013d0f68c062055180dd4328cc8b9d1f30b8",
+ "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/211ebc399c6e73c225a018435fe5ae209d1d1485",
+ "reference": "211ebc399c6e73c225a018435fe5ae209d1d1485",
"shasum": ""
},
"require": {
- "paragonie/constant_time_encoding": "^1|^2",
+ "paragonie/constant_time_encoding": "^1|^2|^3",
"paragonie/random_compat": "^1.4|^2.0|^9.99.99",
"php": ">=5.6.1"
},
@@ -6290,7 +5911,7 @@
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
- "source": "https://github.com/phpseclib/phpseclib/tree/3.0.37"
+ "source": "https://github.com/phpseclib/phpseclib/tree/3.0.39"
},
"funding": [
{
@@ -6306,20 +5927,20 @@
"type": "tidelift"
}
],
- "time": "2024-03-03T02:14:58+00:00"
+ "time": "2024-06-24T06:27:33+00:00"
},
{
"name": "phpstan/phpdoc-parser",
- "version": "1.29.0",
+ "version": "1.29.1",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git",
- "reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc"
+ "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/536889f2b340489d328f5ffb7b02bb6b183ddedc",
- "reference": "536889f2b340489d328f5ffb7b02bb6b183ddedc",
+ "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fcaefacf2d5c417e928405b71b400d4ce10daaf4",
+ "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4",
"shasum": ""
},
"require": {
@@ -6351,22 +5972,22 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
- "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.0"
+ "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.1"
},
- "time": "2024-05-06T12:04:23+00:00"
+ "time": "2024-05-31T08:52:43+00:00"
},
{
"name": "phpstan/phpstan",
- "version": "1.11.2",
+ "version": "1.11.7",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
- "reference": "0d5d4294a70deb7547db655c47685d680e39cfec"
+ "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0d5d4294a70deb7547db655c47685d680e39cfec",
- "reference": "0d5d4294a70deb7547db655c47685d680e39cfec",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/52d2bbfdcae7f895915629e4694e9497d0f8e28d",
+ "reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d",
"shasum": ""
},
"require": {
@@ -6411,60 +6032,7 @@
"type": "github"
}
],
- "time": "2024-05-24T13:23:04+00:00"
- },
- {
- "name": "pimple/pimple",
- "version": "v3.5.0",
- "source": {
- "type": "git",
- "url": "https://github.com/silexphp/Pimple.git",
- "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a94b3a4db7fb774b3d78dad2315ddc07629e1bed",
- "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed",
- "shasum": ""
- },
- "require": {
- "php": ">=7.2.5",
- "psr/container": "^1.1 || ^2.0"
- },
- "require-dev": {
- "symfony/phpunit-bridge": "^5.4@dev"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.4.x-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "Pimple": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- }
- ],
- "description": "Pimple, a simple Dependency Injection Container",
- "homepage": "https://pimple.symfony.com",
- "keywords": [
- "container",
- "dependency injection"
- ],
- "support": {
- "source": "https://github.com/silexphp/Pimple/tree/v3.5.0"
- },
- "time": "2021-10-28T11:13:42+00:00"
+ "time": "2024-07-06T11:17:41+00:00"
},
{
"name": "pion/laravel-chunk-upload",
@@ -7091,16 +6659,16 @@
},
{
"name": "psy/psysh",
- "version": "v0.12.3",
+ "version": "v0.12.4",
"source": {
"type": "git",
"url": "https://github.com/bobthecow/psysh.git",
- "reference": "b6b6cce7d3ee8fbf31843edce5e8f5a72eff4a73"
+ "reference": "2fd717afa05341b4f8152547f142cd2f130f6818"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/bobthecow/psysh/zipball/b6b6cce7d3ee8fbf31843edce5e8f5a72eff4a73",
- "reference": "b6b6cce7d3ee8fbf31843edce5e8f5a72eff4a73",
+ "url": "https://api.github.com/repos/bobthecow/psysh/zipball/2fd717afa05341b4f8152547f142cd2f130f6818",
+ "reference": "2fd717afa05341b4f8152547f142cd2f130f6818",
"shasum": ""
},
"require": {
@@ -7164,9 +6732,9 @@
],
"support": {
"issues": "https://github.com/bobthecow/psysh/issues",
- "source": "https://github.com/bobthecow/psysh/tree/v0.12.3"
+ "source": "https://github.com/bobthecow/psysh/tree/v0.12.4"
},
- "time": "2024-04-02T15:57:53+00:00"
+ "time": "2024-06-10T01:18:23+00:00"
},
{
"name": "purplepixie/phpdns",
@@ -7504,16 +7072,16 @@
},
{
"name": "rector/rector",
- "version": "1.1.0",
+ "version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/rectorphp/rector.git",
- "reference": "556509e2dcf527369892b7d411379c4a02f31859"
+ "reference": "2fa387553db22b6f9bcccf5ff16f2c2c18a52a65"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/rectorphp/rector/zipball/556509e2dcf527369892b7d411379c4a02f31859",
- "reference": "556509e2dcf527369892b7d411379c4a02f31859",
+ "url": "https://api.github.com/repos/rectorphp/rector/zipball/2fa387553db22b6f9bcccf5ff16f2c2c18a52a65",
+ "reference": "2fa387553db22b6f9bcccf5ff16f2c2c18a52a65",
"shasum": ""
},
"require": {
@@ -7551,7 +7119,7 @@
],
"support": {
"issues": "https://github.com/rectorphp/rector/issues",
- "source": "https://github.com/rectorphp/rector/tree/1.1.0"
+ "source": "https://github.com/rectorphp/rector/tree/1.2.0"
},
"funding": [
{
@@ -7559,132 +7127,7 @@
"type": "github"
}
],
- "time": "2024-05-18T09:40:27+00:00"
- },
- {
- "name": "resend/resend-laravel",
- "version": "v0.5.0",
- "source": {
- "type": "git",
- "url": "https://github.com/resend/resend-laravel.git",
- "reference": "e598d1e25e49a7aa4c35f653d1d828f69ee4fc1d"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/resend/resend-laravel/zipball/e598d1e25e49a7aa4c35f653d1d828f69ee4fc1d",
- "reference": "e598d1e25e49a7aa4c35f653d1d828f69ee4fc1d",
- "shasum": ""
- },
- "require": {
- "illuminate/support": "^9.21|^10.0",
- "php": "^8.1",
- "resend/resend-php": "^0.7.1",
- "symfony/mailer": "^6.2"
- },
- "require-dev": {
- "friendsofphp/php-cs-fixer": "^3.14",
- "mockery/mockery": "^1.5",
- "orchestra/testbench": "^7.22|^8.0",
- "pestphp/pest": "^1.22"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-main": "1.x-dev"
- },
- "laravel": {
- "providers": [
- "Resend\\Laravel\\ResendServiceProvider"
- ]
- }
- },
- "autoload": {
- "psr-4": {
- "Resend\\Laravel\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Resend and contributors",
- "homepage": "https://github.com/resendlabs/resend-laravel/contributors"
- }
- ],
- "description": "Resend for Laravel",
- "homepage": "https://resend.com/",
- "keywords": [
- "api",
- "client",
- "laravel",
- "php",
- "resend",
- "sdk"
- ],
- "support": {
- "issues": "https://github.com/resend/resend-laravel/issues",
- "source": "https://github.com/resend/resend-laravel/tree/v0.5.0"
- },
- "time": "2023-07-15T17:56:14+00:00"
- },
- {
- "name": "resend/resend-php",
- "version": "v0.7.2",
- "source": {
- "type": "git",
- "url": "https://github.com/resend/resend-php.git",
- "reference": "bef429c2cd43ae1a1d990059c73750d46f249872"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/resend/resend-php/zipball/bef429c2cd43ae1a1d990059c73750d46f249872",
- "reference": "bef429c2cd43ae1a1d990059c73750d46f249872",
- "shasum": ""
- },
- "require": {
- "guzzlehttp/guzzle": "^7.5",
- "php": "^8.1.0"
- },
- "require-dev": {
- "friendsofphp/php-cs-fixer": "^3.13",
- "pestphp/pest": "^2.0",
- "pestphp/pest-plugin-mock": "^2.0"
- },
- "type": "library",
- "autoload": {
- "files": [
- "src/Resend.php"
- ],
- "psr-4": {
- "Resend\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Resend and contributors",
- "homepage": "https://github.com/resendlabs/resend-php/contributors"
- }
- ],
- "description": "Resend PHP library.",
- "homepage": "https://resend.com/",
- "keywords": [
- "api",
- "client",
- "php",
- "resend",
- "sdk"
- ],
- "support": {
- "issues": "https://github.com/resend/resend-php/issues",
- "source": "https://github.com/resend/resend-php/tree/v0.7.2"
- },
- "time": "2023-09-08T23:47:23+00:00"
+ "time": "2024-07-01T14:24:45+00:00"
},
{
"name": "revolt/event-loop",
@@ -7758,112 +7201,42 @@
},
"time": "2023-11-30T05:34:44+00:00"
},
- {
- "name": "sentry/sdk",
- "version": "3.6.0",
- "source": {
- "type": "git",
- "url": "https://github.com/getsentry/sentry-php-sdk.git",
- "reference": "24c235ff2027401cbea099bf88689e1a1f197c7a"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/getsentry/sentry-php-sdk/zipball/24c235ff2027401cbea099bf88689e1a1f197c7a",
- "reference": "24c235ff2027401cbea099bf88689e1a1f197c7a",
- "shasum": ""
- },
- "require": {
- "http-interop/http-factory-guzzle": "^1.0",
- "sentry/sentry": "^3.22",
- "symfony/http-client": "^4.3|^5.0|^6.0|^7.0"
- },
- "type": "metapackage",
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Sentry",
- "email": "accounts@sentry.io"
- }
- ],
- "description": "This is a metapackage shipping sentry/sentry with a recommended HTTP client.",
- "homepage": "http://sentry.io",
- "keywords": [
- "crash-reporting",
- "crash-reports",
- "error-handler",
- "error-monitoring",
- "log",
- "logging",
- "sentry"
- ],
- "support": {
- "issues": "https://github.com/getsentry/sentry-php-sdk/issues",
- "source": "https://github.com/getsentry/sentry-php-sdk/tree/3.6.0"
- },
- "funding": [
- {
- "url": "https://sentry.io/",
- "type": "custom"
- },
- {
- "url": "https://sentry.io/pricing/",
- "type": "custom"
- }
- ],
- "time": "2023-12-04T10:49:33+00:00"
- },
{
"name": "sentry/sentry",
- "version": "3.22.1",
+ "version": "4.8.0",
"source": {
"type": "git",
"url": "https://github.com/getsentry/sentry-php.git",
- "reference": "8859631ba5ab15bc1af420b0eeed19ecc6c9d81d"
+ "reference": "3cf5778ff425a23f2d22ed41b423691d36f47163"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/8859631ba5ab15bc1af420b0eeed19ecc6c9d81d",
- "reference": "8859631ba5ab15bc1af420b0eeed19ecc6c9d81d",
+ "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/3cf5778ff425a23f2d22ed41b423691d36f47163",
+ "reference": "3cf5778ff425a23f2d22ed41b423691d36f47163",
"shasum": ""
},
"require": {
+ "ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*",
- "guzzlehttp/promises": "^1.5.3|^2.0",
+ "guzzlehttp/psr7": "^1.8.4|^2.1.1",
"jean85/pretty-package-versions": "^1.5|^2.0.4",
"php": "^7.2|^8.0",
- "php-http/async-client-implementation": "^1.0",
- "php-http/client-common": "^1.5|^2.0",
- "php-http/discovery": "^1.15",
- "php-http/httplug": "^1.1|^2.0",
- "php-http/message": "^1.5",
- "php-http/message-factory": "^1.1",
- "psr/http-factory": "^1.0",
- "psr/http-factory-implementation": "^1.0",
"psr/log": "^1.0|^2.0|^3.0",
- "symfony/options-resolver": "^3.4.43|^4.4.30|^5.0.11|^6.0|^7.0",
- "symfony/polyfill-php80": "^1.17"
+ "symfony/options-resolver": "^4.4.30|^5.0.11|^6.0|^7.0"
},
"conflict": {
- "php-http/client-common": "1.8.0",
"raven/raven": "*"
},
"require-dev": {
- "friendsofphp/php-cs-fixer": "^2.19|3.4.*",
+ "friendsofphp/php-cs-fixer": "^3.4",
+ "guzzlehttp/promises": "^1.0|^2.0",
"guzzlehttp/psr7": "^1.8.4|^2.1.1",
- "http-interop/http-factory-guzzle": "^1.0",
"monolog/monolog": "^1.6|^2.0|^3.0",
- "nikic/php-parser": "^4.10.3",
- "php-http/mock-client": "^1.3",
"phpbench/phpbench": "^1.0",
- "phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "^1.3",
- "phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^8.5.14|^9.4",
- "symfony/phpunit-bridge": "^5.2|^6.0",
+ "symfony/phpunit-bridge": "^5.2|^6.0|^7.0",
"vimeo/psalm": "^4.17"
},
"suggest": {
@@ -7888,7 +7261,7 @@
"email": "accounts@sentry.io"
}
],
- "description": "A PHP SDK for Sentry (http://sentry.io)",
+ "description": "PHP SDK for Sentry (http://sentry.io)",
"homepage": "http://sentry.io",
"keywords": [
"crash-reporting",
@@ -7897,11 +7270,13 @@
"error-monitoring",
"log",
"logging",
- "sentry"
+ "profiling",
+ "sentry",
+ "tracing"
],
"support": {
"issues": "https://github.com/getsentry/sentry-php/issues",
- "source": "https://github.com/getsentry/sentry-php/tree/3.22.1"
+ "source": "https://github.com/getsentry/sentry-php/tree/4.8.0"
},
"funding": [
{
@@ -7913,47 +7288,42 @@
"type": "custom"
}
],
- "time": "2023-11-13T11:47:28+00:00"
+ "time": "2024-06-05T13:18:43+00:00"
},
{
"name": "sentry/sentry-laravel",
- "version": "3.8.2",
+ "version": "4.6.1",
"source": {
"type": "git",
"url": "https://github.com/getsentry/sentry-laravel.git",
- "reference": "1293e5732f8405e12f000cdf5dee78c927a18de0"
+ "reference": "7f5fd9f362e440c4c0c492f386b93095321f9101"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/1293e5732f8405e12f000cdf5dee78c927a18de0",
- "reference": "1293e5732f8405e12f000cdf5dee78c927a18de0",
+ "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/7f5fd9f362e440c4c0c492f386b93095321f9101",
+ "reference": "7f5fd9f362e440c4c0c492f386b93095321f9101",
"shasum": ""
},
"require": {
- "illuminate/support": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0",
+ "illuminate/support": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0",
"nyholm/psr7": "^1.0",
"php": "^7.2 | ^8.0",
- "sentry/sdk": "^3.4",
- "sentry/sentry": "^3.20.1",
- "symfony/psr-http-message-bridge": "^1.0 | ^2.0"
+ "sentry/sentry": "^4.7",
+ "symfony/psr-http-message-bridge": "^1.0 | ^2.0 | ^6.0 | ^7.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.11",
- "laravel/folio": "^1.0",
- "laravel/framework": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0",
+ "guzzlehttp/guzzle": "^7.2",
+ "laravel/folio": "^1.1",
+ "laravel/framework": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0",
+ "livewire/livewire": "^2.0 | ^3.0",
"mockery/mockery": "^1.3",
- "orchestra/testbench": "^4.7 | ^5.1 | ^6.0 | ^7.0 | ^8.0",
+ "orchestra/testbench": "^4.7 | ^5.1 | ^6.0 | ^7.0 | ^8.0 | ^9.0",
"phpstan/phpstan": "^1.10",
- "phpunit/phpunit": "^8.4 | ^9.3"
+ "phpunit/phpunit": "^8.4 | ^9.3 | ^10.4"
},
"type": "library",
"extra": {
- "branch-alias": {
- "dev-master": "3.x-dev",
- "dev-2.x": "2.x-dev",
- "dev-1.x": "1.x-dev",
- "dev-0.x": "0.x-dev"
- },
"laravel": {
"providers": [
"Sentry\\Laravel\\ServiceProvider",
@@ -7989,11 +7359,13 @@
"laravel",
"log",
"logging",
- "sentry"
+ "profiling",
+ "sentry",
+ "tracing"
],
"support": {
"issues": "https://github.com/getsentry/sentry-laravel/issues",
- "source": "https://github.com/getsentry/sentry-laravel/tree/3.8.2"
+ "source": "https://github.com/getsentry/sentry-laravel/tree/4.6.1"
},
"funding": [
{
@@ -8005,7 +7377,7 @@
"type": "custom"
}
],
- "time": "2023-10-12T14:38:46+00:00"
+ "time": "2024-06-18T15:06:09+00:00"
},
{
"name": "socialiteproviders/manager",
@@ -8433,16 +7805,16 @@
},
{
"name": "spatie/laravel-ray",
- "version": "1.36.2",
+ "version": "1.37.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-ray.git",
- "reference": "1852faa96e5aa6778ea3401ec3176eee77268718"
+ "reference": "f57b294a3815be37effa9d13f54f2fbe5a2fff37"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/spatie/laravel-ray/zipball/1852faa96e5aa6778ea3401ec3176eee77268718",
- "reference": "1852faa96e5aa6778ea3401ec3176eee77268718",
+ "url": "https://api.github.com/repos/spatie/laravel-ray/zipball/f57b294a3815be37effa9d13f54f2fbe5a2fff37",
+ "reference": "f57b294a3815be37effa9d13f54f2fbe5a2fff37",
"shasum": ""
},
"require": {
@@ -8456,7 +7828,7 @@
"spatie/backtrace": "^1.0",
"spatie/ray": "^1.41.1",
"symfony/stopwatch": "4.2|^5.1|^6.0|^7.0",
- "zbateson/mail-mime-parser": "^1.3.1|^2.0"
+ "zbateson/mail-mime-parser": "^1.3.1|^2.0|^3.0"
},
"require-dev": {
"guzzlehttp/guzzle": "^7.3",
@@ -8504,7 +7876,7 @@
],
"support": {
"issues": "https://github.com/spatie/laravel-ray/issues",
- "source": "https://github.com/spatie/laravel-ray/tree/1.36.2"
+ "source": "https://github.com/spatie/laravel-ray/tree/1.37.0"
},
"funding": [
{
@@ -8516,7 +7888,7 @@
"type": "other"
}
],
- "time": "2024-05-02T08:26:02+00:00"
+ "time": "2024-07-03T08:48:44+00:00"
},
{
"name": "spatie/laravel-schemaless-attributes",
@@ -8931,48 +8303,121 @@
"time": "2023-10-16T18:04:12+00:00"
},
{
- "name": "symfony/console",
- "version": "v6.4.7",
+ "name": "symfony/clock",
+ "version": "v7.1.1",
"source": {
"type": "git",
- "url": "https://github.com/symfony/console.git",
- "reference": "a170e64ae10d00ba89e2acbb590dc2e54da8ad8f"
+ "url": "https://github.com/symfony/clock.git",
+ "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/a170e64ae10d00ba89e2acbb590dc2e54da8ad8f",
- "reference": "a170e64ae10d00ba89e2acbb590dc2e54da8ad8f",
+ "url": "https://api.github.com/repos/symfony/clock/zipball/3dfc8b084853586de51dd1441c6242c76a28cbe7",
+ "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7",
"shasum": ""
},
"require": {
- "php": ">=8.1",
- "symfony/deprecation-contracts": "^2.5|^3",
+ "php": ">=8.2",
+ "psr/clock": "^1.0",
+ "symfony/polyfill-php83": "^1.28"
+ },
+ "provide": {
+ "psr/clock-implementation": "1.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "Resources/now.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\Clock\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Decouples applications from the system clock",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "clock",
+ "psr20",
+ "time"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/clock/tree/v7.1.1"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-05-31T14:57:53+00:00"
+ },
+ {
+ "name": "symfony/console",
+ "version": "v7.1.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/console.git",
+ "reference": "0aa29ca177f432ab68533432db0de059f39c92ae"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/console/zipball/0aa29ca177f432ab68533432db0de059f39c92ae",
+ "reference": "0aa29ca177f432ab68533432db0de059f39c92ae",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
"symfony/polyfill-mbstring": "~1.0",
"symfony/service-contracts": "^2.5|^3",
- "symfony/string": "^5.4|^6.0|^7.0"
+ "symfony/string": "^6.4|^7.0"
},
"conflict": {
- "symfony/dependency-injection": "<5.4",
- "symfony/dotenv": "<5.4",
- "symfony/event-dispatcher": "<5.4",
- "symfony/lock": "<5.4",
- "symfony/process": "<5.4"
+ "symfony/dependency-injection": "<6.4",
+ "symfony/dotenv": "<6.4",
+ "symfony/event-dispatcher": "<6.4",
+ "symfony/lock": "<6.4",
+ "symfony/process": "<6.4"
},
"provide": {
"psr/log-implementation": "1.0|2.0|3.0"
},
"require-dev": {
"psr/log": "^1|^2|^3",
- "symfony/config": "^5.4|^6.0|^7.0",
- "symfony/dependency-injection": "^5.4|^6.0|^7.0",
- "symfony/event-dispatcher": "^5.4|^6.0|^7.0",
+ "symfony/config": "^6.4|^7.0",
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/event-dispatcher": "^6.4|^7.0",
"symfony/http-foundation": "^6.4|^7.0",
"symfony/http-kernel": "^6.4|^7.0",
- "symfony/lock": "^5.4|^6.0|^7.0",
- "symfony/messenger": "^5.4|^6.0|^7.0",
- "symfony/process": "^5.4|^6.0|^7.0",
- "symfony/stopwatch": "^5.4|^6.0|^7.0",
- "symfony/var-dumper": "^5.4|^6.0|^7.0"
+ "symfony/lock": "^6.4|^7.0",
+ "symfony/messenger": "^6.4|^7.0",
+ "symfony/process": "^6.4|^7.0",
+ "symfony/stopwatch": "^6.4|^7.0",
+ "symfony/var-dumper": "^6.4|^7.0"
},
"type": "library",
"autoload": {
@@ -9006,7 +8451,7 @@
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v6.4.7"
+ "source": "https://github.com/symfony/console/tree/v7.1.2"
},
"funding": [
{
@@ -9022,20 +8467,20 @@
"type": "tidelift"
}
],
- "time": "2024-04-18T09:22:46+00:00"
+ "time": "2024-06-28T10:03:55+00:00"
},
{
"name": "symfony/css-selector",
- "version": "v7.0.7",
+ "version": "v7.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
- "reference": "b08a4ad89e84b29cec285b7b1f781a7ae51cf4bc"
+ "reference": "1c7cee86c6f812896af54434f8ce29c8d94f9ff4"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/css-selector/zipball/b08a4ad89e84b29cec285b7b1f781a7ae51cf4bc",
- "reference": "b08a4ad89e84b29cec285b7b1f781a7ae51cf4bc",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/1c7cee86c6f812896af54434f8ce29c8d94f9ff4",
+ "reference": "1c7cee86c6f812896af54434f8ce29c8d94f9ff4",
"shasum": ""
},
"require": {
@@ -9071,7 +8516,7 @@
"description": "Converts CSS selectors to XPath expressions",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/css-selector/tree/v7.0.7"
+ "source": "https://github.com/symfony/css-selector/tree/v7.1.1"
},
"funding": [
{
@@ -9087,7 +8532,7 @@
"type": "tidelift"
}
],
- "time": "2024-04-18T09:29:19+00:00"
+ "time": "2024-05-31T14:57:53+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -9158,22 +8603,22 @@
},
{
"name": "symfony/error-handler",
- "version": "v6.4.7",
+ "version": "v7.1.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/error-handler.git",
- "reference": "667a072466c6a53827ed7b119af93806b884cbb3"
+ "reference": "2412d3dddb5c9ea51a39cfbff1c565fc9844ca32"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/error-handler/zipball/667a072466c6a53827ed7b119af93806b884cbb3",
- "reference": "667a072466c6a53827ed7b119af93806b884cbb3",
+ "url": "https://api.github.com/repos/symfony/error-handler/zipball/2412d3dddb5c9ea51a39cfbff1c565fc9844ca32",
+ "reference": "2412d3dddb5c9ea51a39cfbff1c565fc9844ca32",
"shasum": ""
},
"require": {
- "php": ">=8.1",
+ "php": ">=8.2",
"psr/log": "^1|^2|^3",
- "symfony/var-dumper": "^5.4|^6.0|^7.0"
+ "symfony/var-dumper": "^6.4|^7.0"
},
"conflict": {
"symfony/deprecation-contracts": "<2.5",
@@ -9182,7 +8627,7 @@
"require-dev": {
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/http-kernel": "^6.4|^7.0",
- "symfony/serializer": "^5.4|^6.0|^7.0"
+ "symfony/serializer": "^6.4|^7.0"
},
"bin": [
"Resources/bin/patch-type-declarations"
@@ -9213,7 +8658,7 @@
"description": "Provides tools to manage errors and ease debugging PHP code",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/error-handler/tree/v6.4.7"
+ "source": "https://github.com/symfony/error-handler/tree/v7.1.2"
},
"funding": [
{
@@ -9229,20 +8674,20 @@
"type": "tidelift"
}
],
- "time": "2024-04-18T09:22:46+00:00"
+ "time": "2024-06-25T19:55:06+00:00"
},
{
"name": "symfony/event-dispatcher",
- "version": "v7.0.7",
+ "version": "v7.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "db2a7fab994d67d92356bb39c367db115d9d30f9"
+ "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/db2a7fab994d67d92356bb39c367db115d9d30f9",
- "reference": "db2a7fab994d67d92356bb39c367db115d9d30f9",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7",
+ "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7",
"shasum": ""
},
"require": {
@@ -9293,7 +8738,7 @@
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/event-dispatcher/tree/v7.0.7"
+ "source": "https://github.com/symfony/event-dispatcher/tree/v7.1.1"
},
"funding": [
{
@@ -9309,7 +8754,7 @@
"type": "tidelift"
}
],
- "time": "2024-04-18T09:29:19+00:00"
+ "time": "2024-05-31T14:57:53+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
@@ -9389,23 +8834,23 @@
},
{
"name": "symfony/finder",
- "version": "v6.4.7",
+ "version": "v7.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "511c48990be17358c23bf45c5d71ab85d40fb764"
+ "reference": "fbb0ba67688b780efbc886c1a0a0948dcf7205d6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/511c48990be17358c23bf45c5d71ab85d40fb764",
- "reference": "511c48990be17358c23bf45c5d71ab85d40fb764",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/fbb0ba67688b780efbc886c1a0a0948dcf7205d6",
+ "reference": "fbb0ba67688b780efbc886c1a0a0948dcf7205d6",
"shasum": ""
},
"require": {
- "php": ">=8.1"
+ "php": ">=8.2"
},
"require-dev": {
- "symfony/filesystem": "^6.0|^7.0"
+ "symfony/filesystem": "^6.4|^7.0"
},
"type": "library",
"autoload": {
@@ -9433,7 +8878,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/finder/tree/v6.4.7"
+ "source": "https://github.com/symfony/finder/tree/v7.1.1"
},
"funding": [
{
@@ -9449,211 +8894,40 @@
"type": "tidelift"
}
],
- "time": "2024-04-23T10:36:43+00:00"
- },
- {
- "name": "symfony/http-client",
- "version": "v6.4.7",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/http-client.git",
- "reference": "3683d8107cf1efdd24795cc5f7482be1eded34ac"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/http-client/zipball/3683d8107cf1efdd24795cc5f7482be1eded34ac",
- "reference": "3683d8107cf1efdd24795cc5f7482be1eded34ac",
- "shasum": ""
- },
- "require": {
- "php": ">=8.1",
- "psr/log": "^1|^2|^3",
- "symfony/deprecation-contracts": "^2.5|^3",
- "symfony/http-client-contracts": "^3.4.1",
- "symfony/service-contracts": "^2.5|^3"
- },
- "conflict": {
- "php-http/discovery": "<1.15",
- "symfony/http-foundation": "<6.3"
- },
- "provide": {
- "php-http/async-client-implementation": "*",
- "php-http/client-implementation": "*",
- "psr/http-client-implementation": "1.0",
- "symfony/http-client-implementation": "3.0"
- },
- "require-dev": {
- "amphp/amp": "^2.5",
- "amphp/http-client": "^4.2.1",
- "amphp/http-tunnel": "^1.0",
- "amphp/socket": "^1.1",
- "guzzlehttp/promises": "^1.4|^2.0",
- "nyholm/psr7": "^1.0",
- "php-http/httplug": "^1.0|^2.0",
- "psr/http-client": "^1.0",
- "symfony/dependency-injection": "^5.4|^6.0|^7.0",
- "symfony/http-kernel": "^5.4|^6.0|^7.0",
- "symfony/messenger": "^5.4|^6.0|^7.0",
- "symfony/process": "^5.4|^6.0|^7.0",
- "symfony/stopwatch": "^5.4|^6.0|^7.0"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\HttpClient\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously",
- "homepage": "https://symfony.com",
- "keywords": [
- "http"
- ],
- "support": {
- "source": "https://github.com/symfony/http-client/tree/v6.4.7"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2024-04-18T09:22:46+00:00"
- },
- {
- "name": "symfony/http-client-contracts",
- "version": "v3.5.0",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/http-client-contracts.git",
- "reference": "20414d96f391677bf80078aa55baece78b82647d"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/20414d96f391677bf80078aa55baece78b82647d",
- "reference": "20414d96f391677bf80078aa55baece78b82647d",
- "shasum": ""
- },
- "require": {
- "php": ">=8.1"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-main": "3.5-dev"
- },
- "thanks": {
- "name": "symfony/contracts",
- "url": "https://github.com/symfony/contracts"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Contracts\\HttpClient\\": ""
- },
- "exclude-from-classmap": [
- "/Test/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Generic abstractions related to HTTP clients",
- "homepage": "https://symfony.com",
- "keywords": [
- "abstractions",
- "contracts",
- "decoupling",
- "interfaces",
- "interoperability",
- "standards"
- ],
- "support": {
- "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.0"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2024-04-18T09:32:20+00:00"
+ "time": "2024-05-31T14:57:53+00:00"
},
{
"name": "symfony/http-foundation",
- "version": "v6.4.7",
+ "version": "v7.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "b4db6b833035477cb70e18d0ae33cb7c2b521759"
+ "reference": "74d171d5b6a1d9e4bfee09a41937c17a7536acfa"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/b4db6b833035477cb70e18d0ae33cb7c2b521759",
- "reference": "b4db6b833035477cb70e18d0ae33cb7c2b521759",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/74d171d5b6a1d9e4bfee09a41937c17a7536acfa",
+ "reference": "74d171d5b6a1d9e4bfee09a41937c17a7536acfa",
"shasum": ""
},
"require": {
- "php": ">=8.1",
- "symfony/deprecation-contracts": "^2.5|^3",
+ "php": ">=8.2",
"symfony/polyfill-mbstring": "~1.1",
"symfony/polyfill-php83": "^1.27"
},
"conflict": {
- "symfony/cache": "<6.3"
+ "doctrine/dbal": "<3.6",
+ "symfony/cache": "<6.4"
},
"require-dev": {
- "doctrine/dbal": "^2.13.1|^3|^4",
+ "doctrine/dbal": "^3.6|^4",
"predis/predis": "^1.1|^2.0",
- "symfony/cache": "^6.3|^7.0",
- "symfony/dependency-injection": "^5.4|^6.0|^7.0",
- "symfony/expression-language": "^5.4|^6.0|^7.0",
- "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4|^7.0",
- "symfony/mime": "^5.4|^6.0|^7.0",
- "symfony/rate-limiter": "^5.4|^6.0|^7.0"
+ "symfony/cache": "^6.4|^7.0",
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/expression-language": "^6.4|^7.0",
+ "symfony/http-kernel": "^6.4|^7.0",
+ "symfony/mime": "^6.4|^7.0",
+ "symfony/rate-limiter": "^6.4|^7.0"
},
"type": "library",
"autoload": {
@@ -9681,7 +8955,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-foundation/tree/v6.4.7"
+ "source": "https://github.com/symfony/http-foundation/tree/v7.1.1"
},
"funding": [
{
@@ -9697,77 +8971,77 @@
"type": "tidelift"
}
],
- "time": "2024-04-18T09:22:46+00:00"
+ "time": "2024-05-31T14:57:53+00:00"
},
{
"name": "symfony/http-kernel",
- "version": "v6.4.7",
+ "version": "v7.1.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
- "reference": "b7b5e6cdef670a0c82d015a966ffc7e855861a98"
+ "reference": "ae3fa717db4d41a55d14c2bd92399e37cf5bc0f6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b7b5e6cdef670a0c82d015a966ffc7e855861a98",
- "reference": "b7b5e6cdef670a0c82d015a966ffc7e855861a98",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/ae3fa717db4d41a55d14c2bd92399e37cf5bc0f6",
+ "reference": "ae3fa717db4d41a55d14c2bd92399e37cf5bc0f6",
"shasum": ""
},
"require": {
- "php": ">=8.1",
+ "php": ">=8.2",
"psr/log": "^1|^2|^3",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/error-handler": "^6.4|^7.0",
- "symfony/event-dispatcher": "^5.4|^6.0|^7.0",
+ "symfony/event-dispatcher": "^6.4|^7.0",
"symfony/http-foundation": "^6.4|^7.0",
"symfony/polyfill-ctype": "^1.8"
},
"conflict": {
- "symfony/browser-kit": "<5.4",
- "symfony/cache": "<5.4",
- "symfony/config": "<6.1",
- "symfony/console": "<5.4",
+ "symfony/browser-kit": "<6.4",
+ "symfony/cache": "<6.4",
+ "symfony/config": "<6.4",
+ "symfony/console": "<6.4",
"symfony/dependency-injection": "<6.4",
- "symfony/doctrine-bridge": "<5.4",
- "symfony/form": "<5.4",
- "symfony/http-client": "<5.4",
+ "symfony/doctrine-bridge": "<6.4",
+ "symfony/form": "<6.4",
+ "symfony/http-client": "<6.4",
"symfony/http-client-contracts": "<2.5",
- "symfony/mailer": "<5.4",
- "symfony/messenger": "<5.4",
- "symfony/translation": "<5.4",
+ "symfony/mailer": "<6.4",
+ "symfony/messenger": "<6.4",
+ "symfony/translation": "<6.4",
"symfony/translation-contracts": "<2.5",
- "symfony/twig-bridge": "<5.4",
+ "symfony/twig-bridge": "<6.4",
"symfony/validator": "<6.4",
- "symfony/var-dumper": "<6.3",
- "twig/twig": "<2.13"
+ "symfony/var-dumper": "<6.4",
+ "twig/twig": "<3.0.4"
},
"provide": {
"psr/log-implementation": "1.0|2.0|3.0"
},
"require-dev": {
"psr/cache": "^1.0|^2.0|^3.0",
- "symfony/browser-kit": "^5.4|^6.0|^7.0",
- "symfony/clock": "^6.2|^7.0",
- "symfony/config": "^6.1|^7.0",
- "symfony/console": "^5.4|^6.0|^7.0",
- "symfony/css-selector": "^5.4|^6.0|^7.0",
+ "symfony/browser-kit": "^6.4|^7.0",
+ "symfony/clock": "^6.4|^7.0",
+ "symfony/config": "^6.4|^7.0",
+ "symfony/console": "^6.4|^7.0",
+ "symfony/css-selector": "^6.4|^7.0",
"symfony/dependency-injection": "^6.4|^7.0",
- "symfony/dom-crawler": "^5.4|^6.0|^7.0",
- "symfony/expression-language": "^5.4|^6.0|^7.0",
- "symfony/finder": "^5.4|^6.0|^7.0",
+ "symfony/dom-crawler": "^6.4|^7.0",
+ "symfony/expression-language": "^6.4|^7.0",
+ "symfony/finder": "^6.4|^7.0",
"symfony/http-client-contracts": "^2.5|^3",
- "symfony/process": "^5.4|^6.0|^7.0",
- "symfony/property-access": "^5.4.5|^6.0.5|^7.0",
- "symfony/routing": "^5.4|^6.0|^7.0",
- "symfony/serializer": "^6.4.4|^7.0.4",
- "symfony/stopwatch": "^5.4|^6.0|^7.0",
- "symfony/translation": "^5.4|^6.0|^7.0",
+ "symfony/process": "^6.4|^7.0",
+ "symfony/property-access": "^7.1",
+ "symfony/routing": "^6.4|^7.0",
+ "symfony/serializer": "^7.1",
+ "symfony/stopwatch": "^6.4|^7.0",
+ "symfony/translation": "^6.4|^7.0",
"symfony/translation-contracts": "^2.5|^3",
- "symfony/uid": "^5.4|^6.0|^7.0",
+ "symfony/uid": "^6.4|^7.0",
"symfony/validator": "^6.4|^7.0",
- "symfony/var-dumper": "^5.4|^6.4|^7.0",
- "symfony/var-exporter": "^6.2|^7.0",
- "twig/twig": "^2.13|^3.0.4"
+ "symfony/var-dumper": "^6.4|^7.0",
+ "symfony/var-exporter": "^6.4|^7.0",
+ "twig/twig": "^3.0.4"
},
"type": "library",
"autoload": {
@@ -9795,7 +9069,7 @@
"description": "Provides a structured process for converting a Request into a Response",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/http-kernel/tree/v6.4.7"
+ "source": "https://github.com/symfony/http-kernel/tree/v7.1.2"
},
"funding": [
{
@@ -9811,43 +9085,43 @@
"type": "tidelift"
}
],
- "time": "2024-04-29T11:24:44+00:00"
+ "time": "2024-06-28T13:13:31+00:00"
},
{
"name": "symfony/mailer",
- "version": "v6.4.7",
+ "version": "v7.1.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/mailer.git",
- "reference": "2c446d4e446995bed983c0b5bb9ff837e8de7dbd"
+ "reference": "8fcff0af9043c8f8a8e229437cea363e282f9aee"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/mailer/zipball/2c446d4e446995bed983c0b5bb9ff837e8de7dbd",
- "reference": "2c446d4e446995bed983c0b5bb9ff837e8de7dbd",
+ "url": "https://api.github.com/repos/symfony/mailer/zipball/8fcff0af9043c8f8a8e229437cea363e282f9aee",
+ "reference": "8fcff0af9043c8f8a8e229437cea363e282f9aee",
"shasum": ""
},
"require": {
"egulias/email-validator": "^2.1.10|^3|^4",
- "php": ">=8.1",
+ "php": ">=8.2",
"psr/event-dispatcher": "^1",
"psr/log": "^1|^2|^3",
- "symfony/event-dispatcher": "^5.4|^6.0|^7.0",
- "symfony/mime": "^6.2|^7.0",
+ "symfony/event-dispatcher": "^6.4|^7.0",
+ "symfony/mime": "^6.4|^7.0",
"symfony/service-contracts": "^2.5|^3"
},
"conflict": {
"symfony/http-client-contracts": "<2.5",
- "symfony/http-kernel": "<5.4",
- "symfony/messenger": "<6.2",
- "symfony/mime": "<6.2",
- "symfony/twig-bridge": "<6.2.1"
+ "symfony/http-kernel": "<6.4",
+ "symfony/messenger": "<6.4",
+ "symfony/mime": "<6.4",
+ "symfony/twig-bridge": "<6.4"
},
"require-dev": {
- "symfony/console": "^5.4|^6.0|^7.0",
- "symfony/http-client": "^5.4|^6.0|^7.0",
- "symfony/messenger": "^6.2|^7.0",
- "symfony/twig-bridge": "^6.2|^7.0"
+ "symfony/console": "^6.4|^7.0",
+ "symfony/http-client": "^6.4|^7.0",
+ "symfony/messenger": "^6.4|^7.0",
+ "symfony/twig-bridge": "^6.4|^7.0"
},
"type": "library",
"autoload": {
@@ -9875,7 +9149,7 @@
"description": "Helps sending emails",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/mailer/tree/v6.4.7"
+ "source": "https://github.com/symfony/mailer/tree/v7.1.2"
},
"funding": [
{
@@ -9891,25 +9165,24 @@
"type": "tidelift"
}
],
- "time": "2024-04-18T09:22:46+00:00"
+ "time": "2024-06-28T08:00:31+00:00"
},
{
"name": "symfony/mime",
- "version": "v6.4.7",
+ "version": "v7.1.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/mime.git",
- "reference": "decadcf3865918ecfcbfa90968553994ce935a5e"
+ "reference": "26a00b85477e69a4bab63b66c5dce64f18b0cbfc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/mime/zipball/decadcf3865918ecfcbfa90968553994ce935a5e",
- "reference": "decadcf3865918ecfcbfa90968553994ce935a5e",
+ "url": "https://api.github.com/repos/symfony/mime/zipball/26a00b85477e69a4bab63b66c5dce64f18b0cbfc",
+ "reference": "26a00b85477e69a4bab63b66c5dce64f18b0cbfc",
"shasum": ""
},
"require": {
- "php": ">=8.1",
- "symfony/deprecation-contracts": "^2.5|^3",
+ "php": ">=8.2",
"symfony/polyfill-intl-idn": "^1.10",
"symfony/polyfill-mbstring": "^1.0"
},
@@ -9917,18 +9190,18 @@
"egulias/email-validator": "~3.0.0",
"phpdocumentor/reflection-docblock": "<3.2.2",
"phpdocumentor/type-resolver": "<1.4.0",
- "symfony/mailer": "<5.4",
- "symfony/serializer": "<6.3.2"
+ "symfony/mailer": "<6.4",
+ "symfony/serializer": "<6.4.3|>7.0,<7.0.3"
},
"require-dev": {
"egulias/email-validator": "^2.1.10|^3.1|^4",
"league/html-to-markdown": "^5.0",
"phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
- "symfony/dependency-injection": "^5.4|^6.0|^7.0",
- "symfony/process": "^5.4|^6.4|^7.0",
- "symfony/property-access": "^5.4|^6.0|^7.0",
- "symfony/property-info": "^5.4|^6.0|^7.0",
- "symfony/serializer": "^6.3.2|^7.0"
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/process": "^6.4|^7.0",
+ "symfony/property-access": "^6.4|^7.0",
+ "symfony/property-info": "^6.4|^7.0",
+ "symfony/serializer": "^6.4.3|^7.0.3"
},
"type": "library",
"autoload": {
@@ -9960,7 +9233,7 @@
"mime-type"
],
"support": {
- "source": "https://github.com/symfony/mime/tree/v6.4.7"
+ "source": "https://github.com/symfony/mime/tree/v7.1.2"
},
"funding": [
{
@@ -9976,20 +9249,20 @@
"type": "tidelift"
}
],
- "time": "2024-04-18T09:22:46+00:00"
+ "time": "2024-06-28T10:03:55+00:00"
},
{
"name": "symfony/options-resolver",
- "version": "v7.0.7",
+ "version": "v7.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
- "reference": "23cc173858776ad451e31f053b1c9f47840b2cfa"
+ "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/options-resolver/zipball/23cc173858776ad451e31f053b1c9f47840b2cfa",
- "reference": "23cc173858776ad451e31f053b1c9f47840b2cfa",
+ "url": "https://api.github.com/repos/symfony/options-resolver/zipball/47aa818121ed3950acd2b58d1d37d08a94f9bf55",
+ "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55",
"shasum": ""
},
"require": {
@@ -10027,7 +9300,7 @@
"options"
],
"support": {
- "source": "https://github.com/symfony/options-resolver/tree/v7.0.7"
+ "source": "https://github.com/symfony/options-resolver/tree/v7.1.1"
},
"funding": [
{
@@ -10043,20 +9316,20 @@
"type": "tidelift"
}
],
- "time": "2024-04-18T09:29:19+00:00"
+ "time": "2024-05-31T14:57:53+00:00"
},
{
"name": "symfony/polyfill-ctype",
- "version": "v1.29.0",
+ "version": "v1.30.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
- "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4"
+ "reference": "0424dff1c58f028c451efff2045f5d92410bd540"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4",
- "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540",
+ "reference": "0424dff1c58f028c451efff2045f5d92410bd540",
"shasum": ""
},
"require": {
@@ -10106,7 +9379,7 @@
"portable"
],
"support": {
- "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0"
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0"
},
"funding": [
{
@@ -10122,20 +9395,20 @@
"type": "tidelift"
}
],
- "time": "2024-01-29T20:11:03+00:00"
+ "time": "2024-05-31T15:07:36+00:00"
},
{
"name": "symfony/polyfill-iconv",
- "version": "v1.29.0",
+ "version": "v1.30.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-iconv.git",
- "reference": "cd4226d140ecd3d0f13d32ed0a4a095ffe871d2f"
+ "reference": "c027e6a3c6aee334663ec21f5852e89738abc805"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/cd4226d140ecd3d0f13d32ed0a4a095ffe871d2f",
- "reference": "cd4226d140ecd3d0f13d32ed0a4a095ffe871d2f",
+ "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/c027e6a3c6aee334663ec21f5852e89738abc805",
+ "reference": "c027e6a3c6aee334663ec21f5852e89738abc805",
"shasum": ""
},
"require": {
@@ -10186,7 +9459,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-iconv/tree/v1.29.0"
+ "source": "https://github.com/symfony/polyfill-iconv/tree/v1.30.0"
},
"funding": [
{
@@ -10202,20 +9475,20 @@
"type": "tidelift"
}
],
- "time": "2024-01-29T20:11:03+00:00"
+ "time": "2024-05-31T15:07:36+00:00"
},
{
"name": "symfony/polyfill-intl-grapheme",
- "version": "v1.29.0",
+ "version": "v1.30.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
- "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f"
+ "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f",
- "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a",
+ "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a",
"shasum": ""
},
"require": {
@@ -10264,7 +9537,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0"
+ "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0"
},
"funding": [
{
@@ -10280,20 +9553,20 @@
"type": "tidelift"
}
],
- "time": "2024-01-29T20:11:03+00:00"
+ "time": "2024-05-31T15:07:36+00:00"
},
{
"name": "symfony/polyfill-intl-idn",
- "version": "v1.29.0",
+ "version": "v1.30.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-idn.git",
- "reference": "a287ed7475f85bf6f61890146edbc932c0fff919"
+ "reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a287ed7475f85bf6f61890146edbc932c0fff919",
- "reference": "a287ed7475f85bf6f61890146edbc932c0fff919",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a6e83bdeb3c84391d1dfe16f42e40727ce524a5c",
+ "reference": "a6e83bdeb3c84391d1dfe16f42e40727ce524a5c",
"shasum": ""
},
"require": {
@@ -10348,7 +9621,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.29.0"
+ "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.30.0"
},
"funding": [
{
@@ -10364,20 +9637,20 @@
"type": "tidelift"
}
],
- "time": "2024-01-29T20:11:03+00:00"
+ "time": "2024-05-31T15:07:36+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
- "version": "v1.29.0",
+ "version": "v1.30.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
- "reference": "bc45c394692b948b4d383a08d7753968bed9a83d"
+ "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d",
- "reference": "bc45c394692b948b4d383a08d7753968bed9a83d",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb",
+ "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb",
"shasum": ""
},
"require": {
@@ -10429,7 +9702,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0"
+ "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0"
},
"funding": [
{
@@ -10445,20 +9718,20 @@
"type": "tidelift"
}
],
- "time": "2024-01-29T20:11:03+00:00"
+ "time": "2024-05-31T15:07:36+00:00"
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.29.0",
+ "version": "v1.30.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec"
+ "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
- "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c",
+ "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c",
"shasum": ""
},
"require": {
@@ -10509,7 +9782,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0"
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0"
},
"funding": [
{
@@ -10525,20 +9798,20 @@
"type": "tidelift"
}
],
- "time": "2024-01-29T20:11:03+00:00"
+ "time": "2024-06-19T12:30:46+00:00"
},
{
"name": "symfony/polyfill-php72",
- "version": "v1.29.0",
+ "version": "v1.30.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php72.git",
- "reference": "861391a8da9a04cbad2d232ddd9e4893220d6e25"
+ "reference": "10112722600777e02d2745716b70c5db4ca70442"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/861391a8da9a04cbad2d232ddd9e4893220d6e25",
- "reference": "861391a8da9a04cbad2d232ddd9e4893220d6e25",
+ "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/10112722600777e02d2745716b70c5db4ca70442",
+ "reference": "10112722600777e02d2745716b70c5db4ca70442",
"shasum": ""
},
"require": {
@@ -10582,7 +9855,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-php72/tree/v1.29.0"
+ "source": "https://github.com/symfony/polyfill-php72/tree/v1.30.0"
},
"funding": [
{
@@ -10598,20 +9871,20 @@
"type": "tidelift"
}
],
- "time": "2024-01-29T20:11:03+00:00"
+ "time": "2024-06-19T12:30:46+00:00"
},
{
"name": "symfony/polyfill-php80",
- "version": "v1.29.0",
+ "version": "v1.30.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
- "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b"
+ "reference": "77fa7995ac1b21ab60769b7323d600a991a90433"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
- "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433",
+ "reference": "77fa7995ac1b21ab60769b7323d600a991a90433",
"shasum": ""
},
"require": {
@@ -10662,7 +9935,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0"
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0"
},
"funding": [
{
@@ -10678,25 +9951,24 @@
"type": "tidelift"
}
],
- "time": "2024-01-29T20:11:03+00:00"
+ "time": "2024-05-31T15:07:36+00:00"
},
{
"name": "symfony/polyfill-php83",
- "version": "v1.29.0",
+ "version": "v1.30.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php83.git",
- "reference": "86fcae159633351e5fd145d1c47de6c528f8caff"
+ "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/86fcae159633351e5fd145d1c47de6c528f8caff",
- "reference": "86fcae159633351e5fd145d1c47de6c528f8caff",
+ "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9",
+ "reference": "dbdcdf1a4dcc2743591f1079d0c35ab1e2dcbbc9",
"shasum": ""
},
"require": {
- "php": ">=7.1",
- "symfony/polyfill-php80": "^1.14"
+ "php": ">=7.1"
},
"type": "library",
"extra": {
@@ -10739,7 +10011,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-php83/tree/v1.29.0"
+ "source": "https://github.com/symfony/polyfill-php83/tree/v1.30.0"
},
"funding": [
{
@@ -10755,20 +10027,20 @@
"type": "tidelift"
}
],
- "time": "2024-01-29T20:11:03+00:00"
+ "time": "2024-06-19T12:35:24+00:00"
},
{
"name": "symfony/polyfill-uuid",
- "version": "v1.29.0",
+ "version": "v1.30.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-uuid.git",
- "reference": "3abdd21b0ceaa3000ee950097bc3cf9efc137853"
+ "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/3abdd21b0ceaa3000ee950097bc3cf9efc137853",
- "reference": "3abdd21b0ceaa3000ee950097bc3cf9efc137853",
+ "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/2ba1f33797470debcda07fe9dce20a0003df18e9",
+ "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9",
"shasum": ""
},
"require": {
@@ -10818,7 +10090,7 @@
"uuid"
],
"support": {
- "source": "https://github.com/symfony/polyfill-uuid/tree/v1.29.0"
+ "source": "https://github.com/symfony/polyfill-uuid/tree/v1.30.0"
},
"funding": [
{
@@ -10834,24 +10106,24 @@
"type": "tidelift"
}
],
- "time": "2024-01-29T20:11:03+00:00"
+ "time": "2024-05-31T15:07:36+00:00"
},
{
"name": "symfony/process",
- "version": "v6.4.7",
+ "version": "v7.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
- "reference": "cdb1c81c145fd5aa9b0038bab694035020943381"
+ "reference": "febf90124323a093c7ee06fdb30e765ca3c20028"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/cdb1c81c145fd5aa9b0038bab694035020943381",
- "reference": "cdb1c81c145fd5aa9b0038bab694035020943381",
+ "url": "https://api.github.com/repos/symfony/process/zipball/febf90124323a093c7ee06fdb30e765ca3c20028",
+ "reference": "febf90124323a093c7ee06fdb30e765ca3c20028",
"shasum": ""
},
"require": {
- "php": ">=8.1"
+ "php": ">=8.2"
},
"type": "library",
"autoload": {
@@ -10879,7 +10151,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/process/tree/v6.4.7"
+ "source": "https://github.com/symfony/process/tree/v7.1.1"
},
"funding": [
{
@@ -10895,47 +10167,42 @@
"type": "tidelift"
}
],
- "time": "2024-04-18T09:22:46+00:00"
+ "time": "2024-05-31T14:57:53+00:00"
},
{
"name": "symfony/psr-http-message-bridge",
- "version": "v2.3.1",
+ "version": "v7.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/psr-http-message-bridge.git",
- "reference": "581ca6067eb62640de5ff08ee1ba6850a0ee472e"
+ "reference": "9a5dbb606da711f5d40a7596ad577856f9402140"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/581ca6067eb62640de5ff08ee1ba6850a0ee472e",
- "reference": "581ca6067eb62640de5ff08ee1ba6850a0ee472e",
+ "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/9a5dbb606da711f5d40a7596ad577856f9402140",
+ "reference": "9a5dbb606da711f5d40a7596ad577856f9402140",
"shasum": ""
},
"require": {
- "php": ">=7.2.5",
- "psr/http-message": "^1.0 || ^2.0",
- "symfony/deprecation-contracts": "^2.5 || ^3.0",
- "symfony/http-foundation": "^5.4 || ^6.0"
+ "php": ">=8.2",
+ "psr/http-message": "^1.0|^2.0",
+ "symfony/http-foundation": "^6.4|^7.0"
+ },
+ "conflict": {
+ "php-http/discovery": "<1.15",
+ "symfony/http-kernel": "<6.4"
},
"require-dev": {
"nyholm/psr7": "^1.1",
- "psr/log": "^1.1 || ^2 || ^3",
- "symfony/browser-kit": "^5.4 || ^6.0",
- "symfony/config": "^5.4 || ^6.0",
- "symfony/event-dispatcher": "^5.4 || ^6.0",
- "symfony/framework-bundle": "^5.4 || ^6.0",
- "symfony/http-kernel": "^5.4 || ^6.0",
- "symfony/phpunit-bridge": "^6.2"
- },
- "suggest": {
- "nyholm/psr7": "For a super lightweight PSR-7/17 implementation"
+ "php-http/discovery": "^1.15",
+ "psr/log": "^1.1.4|^2|^3",
+ "symfony/browser-kit": "^6.4|^7.0",
+ "symfony/config": "^6.4|^7.0",
+ "symfony/event-dispatcher": "^6.4|^7.0",
+ "symfony/framework-bundle": "^6.4|^7.0",
+ "symfony/http-kernel": "^6.4|^7.0"
},
"type": "symfony-bridge",
- "extra": {
- "branch-alias": {
- "dev-main": "2.3-dev"
- }
- },
"autoload": {
"psr-4": {
"Symfony\\Bridge\\PsrHttpMessage\\": ""
@@ -10955,11 +10222,11 @@
},
{
"name": "Symfony Community",
- "homepage": "http://symfony.com/contributors"
+ "homepage": "https://symfony.com/contributors"
}
],
"description": "PSR HTTP message bridge",
- "homepage": "http://symfony.com",
+ "homepage": "https://symfony.com",
"keywords": [
"http",
"http-message",
@@ -10967,8 +10234,7 @@
"psr-7"
],
"support": {
- "issues": "https://github.com/symfony/psr-http-message-bridge/issues",
- "source": "https://github.com/symfony/psr-http-message-bridge/tree/v2.3.1"
+ "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.1.1"
},
"funding": [
{
@@ -10984,40 +10250,38 @@
"type": "tidelift"
}
],
- "time": "2023-07-26T11:53:26+00:00"
+ "time": "2024-05-31T14:57:53+00:00"
},
{
"name": "symfony/routing",
- "version": "v6.4.7",
+ "version": "v7.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
- "reference": "276e06398f71fa2a973264d94f28150f93cfb907"
+ "reference": "60c31bab5c45af7f13091b87deb708830f3c96c0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/routing/zipball/276e06398f71fa2a973264d94f28150f93cfb907",
- "reference": "276e06398f71fa2a973264d94f28150f93cfb907",
+ "url": "https://api.github.com/repos/symfony/routing/zipball/60c31bab5c45af7f13091b87deb708830f3c96c0",
+ "reference": "60c31bab5c45af7f13091b87deb708830f3c96c0",
"shasum": ""
},
"require": {
- "php": ">=8.1",
+ "php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3"
},
"conflict": {
- "doctrine/annotations": "<1.12",
- "symfony/config": "<6.2",
- "symfony/dependency-injection": "<5.4",
- "symfony/yaml": "<5.4"
+ "symfony/config": "<6.4",
+ "symfony/dependency-injection": "<6.4",
+ "symfony/yaml": "<6.4"
},
"require-dev": {
- "doctrine/annotations": "^1.12|^2",
"psr/log": "^1|^2|^3",
- "symfony/config": "^6.2|^7.0",
- "symfony/dependency-injection": "^5.4|^6.0|^7.0",
- "symfony/expression-language": "^5.4|^6.0|^7.0",
- "symfony/http-foundation": "^5.4|^6.0|^7.0",
- "symfony/yaml": "^5.4|^6.0|^7.0"
+ "symfony/config": "^6.4|^7.0",
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/expression-language": "^6.4|^7.0",
+ "symfony/http-foundation": "^6.4|^7.0",
+ "symfony/yaml": "^6.4|^7.0"
},
"type": "library",
"autoload": {
@@ -11051,7 +10315,7 @@
"url"
],
"support": {
- "source": "https://github.com/symfony/routing/tree/v6.4.7"
+ "source": "https://github.com/symfony/routing/tree/v7.1.1"
},
"funding": [
{
@@ -11067,7 +10331,7 @@
"type": "tidelift"
}
],
- "time": "2024-04-18T09:22:46+00:00"
+ "time": "2024-05-31T14:57:53+00:00"
},
{
"name": "symfony/service-contracts",
@@ -11154,16 +10418,16 @@
},
{
"name": "symfony/stopwatch",
- "version": "v7.0.7",
+ "version": "v7.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/stopwatch.git",
- "reference": "41a7a24aa1dc82adf46a06bc292d1923acfe6b84"
+ "reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/stopwatch/zipball/41a7a24aa1dc82adf46a06bc292d1923acfe6b84",
- "reference": "41a7a24aa1dc82adf46a06bc292d1923acfe6b84",
+ "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d",
+ "reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d",
"shasum": ""
},
"require": {
@@ -11196,7 +10460,7 @@
"description": "Provides a way to profile code",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/stopwatch/tree/v7.0.7"
+ "source": "https://github.com/symfony/stopwatch/tree/v7.1.1"
},
"funding": [
{
@@ -11212,20 +10476,20 @@
"type": "tidelift"
}
],
- "time": "2024-04-18T09:29:19+00:00"
+ "time": "2024-05-31T14:57:53+00:00"
},
{
"name": "symfony/string",
- "version": "v7.0.7",
+ "version": "v7.1.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "e405b5424dc2528e02e31ba26b83a79fd4eb8f63"
+ "reference": "14221089ac66cf82e3cf3d1c1da65de305587ff8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/e405b5424dc2528e02e31ba26b83a79fd4eb8f63",
- "reference": "e405b5424dc2528e02e31ba26b83a79fd4eb8f63",
+ "url": "https://api.github.com/repos/symfony/string/zipball/14221089ac66cf82e3cf3d1c1da65de305587ff8",
+ "reference": "14221089ac66cf82e3cf3d1c1da65de305587ff8",
"shasum": ""
},
"require": {
@@ -11239,6 +10503,7 @@
"symfony/translation-contracts": "<2.5"
},
"require-dev": {
+ "symfony/emoji": "^7.1",
"symfony/error-handler": "^6.4|^7.0",
"symfony/http-client": "^6.4|^7.0",
"symfony/intl": "^6.4|^7.0",
@@ -11282,7 +10547,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v7.0.7"
+ "source": "https://github.com/symfony/string/tree/v7.1.2"
},
"funding": [
{
@@ -11298,37 +10563,36 @@
"type": "tidelift"
}
],
- "time": "2024-04-18T09:29:19+00:00"
+ "time": "2024-06-28T09:27:18+00:00"
},
{
"name": "symfony/translation",
- "version": "v6.4.7",
+ "version": "v7.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "7495687c58bfd88b7883823747b0656d90679123"
+ "reference": "cf5ae136e124fc7681b34ce9fac9d5b9ae8ceee3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/7495687c58bfd88b7883823747b0656d90679123",
- "reference": "7495687c58bfd88b7883823747b0656d90679123",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/cf5ae136e124fc7681b34ce9fac9d5b9ae8ceee3",
+ "reference": "cf5ae136e124fc7681b34ce9fac9d5b9ae8ceee3",
"shasum": ""
},
"require": {
- "php": ">=8.1",
- "symfony/deprecation-contracts": "^2.5|^3",
+ "php": ">=8.2",
"symfony/polyfill-mbstring": "~1.0",
"symfony/translation-contracts": "^2.5|^3.0"
},
"conflict": {
- "symfony/config": "<5.4",
- "symfony/console": "<5.4",
- "symfony/dependency-injection": "<5.4",
+ "symfony/config": "<6.4",
+ "symfony/console": "<6.4",
+ "symfony/dependency-injection": "<6.4",
"symfony/http-client-contracts": "<2.5",
- "symfony/http-kernel": "<5.4",
+ "symfony/http-kernel": "<6.4",
"symfony/service-contracts": "<2.5",
- "symfony/twig-bundle": "<5.4",
- "symfony/yaml": "<5.4"
+ "symfony/twig-bundle": "<6.4",
+ "symfony/yaml": "<6.4"
},
"provide": {
"symfony/translation-implementation": "2.3|3.0"
@@ -11336,17 +10600,17 @@
"require-dev": {
"nikic/php-parser": "^4.18|^5.0",
"psr/log": "^1|^2|^3",
- "symfony/config": "^5.4|^6.0|^7.0",
- "symfony/console": "^5.4|^6.0|^7.0",
- "symfony/dependency-injection": "^5.4|^6.0|^7.0",
- "symfony/finder": "^5.4|^6.0|^7.0",
+ "symfony/config": "^6.4|^7.0",
+ "symfony/console": "^6.4|^7.0",
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/finder": "^6.4|^7.0",
"symfony/http-client-contracts": "^2.5|^3.0",
- "symfony/http-kernel": "^5.4|^6.0|^7.0",
- "symfony/intl": "^5.4|^6.0|^7.0",
+ "symfony/http-kernel": "^6.4|^7.0",
+ "symfony/intl": "^6.4|^7.0",
"symfony/polyfill-intl-icu": "^1.21",
- "symfony/routing": "^5.4|^6.0|^7.0",
+ "symfony/routing": "^6.4|^7.0",
"symfony/service-contracts": "^2.5|^3",
- "symfony/yaml": "^5.4|^6.0|^7.0"
+ "symfony/yaml": "^6.4|^7.0"
},
"type": "library",
"autoload": {
@@ -11377,7 +10641,7 @@
"description": "Provides tools to internationalize your application",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/translation/tree/v6.4.7"
+ "source": "https://github.com/symfony/translation/tree/v7.1.1"
},
"funding": [
{
@@ -11393,7 +10657,7 @@
"type": "tidelift"
}
],
- "time": "2024-04-18T09:22:46+00:00"
+ "time": "2024-05-31T14:57:53+00:00"
},
{
"name": "symfony/translation-contracts",
@@ -11475,24 +10739,24 @@
},
{
"name": "symfony/uid",
- "version": "v6.4.7",
+ "version": "v7.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/uid.git",
- "reference": "a66efcb71d8bc3a207d9d78e0bd67f3321510355"
+ "reference": "bb59febeecc81528ff672fad5dab7f06db8c8277"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/uid/zipball/a66efcb71d8bc3a207d9d78e0bd67f3321510355",
- "reference": "a66efcb71d8bc3a207d9d78e0bd67f3321510355",
+ "url": "https://api.github.com/repos/symfony/uid/zipball/bb59febeecc81528ff672fad5dab7f06db8c8277",
+ "reference": "bb59febeecc81528ff672fad5dab7f06db8c8277",
"shasum": ""
},
"require": {
- "php": ">=8.1",
+ "php": ">=8.2",
"symfony/polyfill-uuid": "^1.15"
},
"require-dev": {
- "symfony/console": "^5.4|^6.0|^7.0"
+ "symfony/console": "^6.4|^7.0"
},
"type": "library",
"autoload": {
@@ -11529,7 +10793,7 @@
"uuid"
],
"support": {
- "source": "https://github.com/symfony/uid/tree/v6.4.7"
+ "source": "https://github.com/symfony/uid/tree/v7.1.1"
},
"funding": [
{
@@ -11545,38 +10809,36 @@
"type": "tidelift"
}
],
- "time": "2024-04-18T09:22:46+00:00"
+ "time": "2024-05-31T14:57:53+00:00"
},
{
"name": "symfony/var-dumper",
- "version": "v6.4.7",
+ "version": "v7.1.2",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
- "reference": "7a9cd977cd1c5fed3694bee52990866432af07d7"
+ "reference": "5857c57c6b4b86524c08cf4f4bc95327270a816d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/7a9cd977cd1c5fed3694bee52990866432af07d7",
- "reference": "7a9cd977cd1c5fed3694bee52990866432af07d7",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/5857c57c6b4b86524c08cf4f4bc95327270a816d",
+ "reference": "5857c57c6b4b86524c08cf4f4bc95327270a816d",
"shasum": ""
},
"require": {
- "php": ">=8.1",
- "symfony/deprecation-contracts": "^2.5|^3",
+ "php": ">=8.2",
"symfony/polyfill-mbstring": "~1.0"
},
"conflict": {
- "symfony/console": "<5.4"
+ "symfony/console": "<6.4"
},
"require-dev": {
"ext-iconv": "*",
- "symfony/console": "^5.4|^6.0|^7.0",
- "symfony/error-handler": "^6.3|^7.0",
- "symfony/http-kernel": "^5.4|^6.0|^7.0",
- "symfony/process": "^5.4|^6.0|^7.0",
- "symfony/uid": "^5.4|^6.0|^7.0",
- "twig/twig": "^2.13|^3.0.4"
+ "symfony/console": "^6.4|^7.0",
+ "symfony/http-kernel": "^6.4|^7.0",
+ "symfony/process": "^6.4|^7.0",
+ "symfony/uid": "^6.4|^7.0",
+ "twig/twig": "^3.0.4"
},
"bin": [
"Resources/bin/var-dump-server"
@@ -11614,7 +10876,7 @@
"dump"
],
"support": {
- "source": "https://github.com/symfony/var-dumper/tree/v6.4.7"
+ "source": "https://github.com/symfony/var-dumper/tree/v7.1.2"
},
"funding": [
{
@@ -11630,20 +10892,20 @@
"type": "tidelift"
}
],
- "time": "2024-04-18T09:22:46+00:00"
+ "time": "2024-06-28T08:00:31+00:00"
},
{
"name": "symfony/yaml",
- "version": "v6.4.7",
+ "version": "v6.4.8",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "53e8b1ef30a65f78eac60fddc5ee7ebbbdb1dee0"
+ "reference": "52903de178d542850f6f341ba92995d3d63e60c9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/53e8b1ef30a65f78eac60fddc5ee7ebbbdb1dee0",
- "reference": "53e8b1ef30a65f78eac60fddc5ee7ebbbdb1dee0",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/52903de178d542850f6f341ba92995d3d63e60c9",
+ "reference": "52903de178d542850f6f341ba92995d3d63e60c9",
"shasum": ""
},
"require": {
@@ -11686,7 +10948,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
- "source": "https://github.com/symfony/yaml/tree/v6.4.7"
+ "source": "https://github.com/symfony/yaml/tree/v6.4.8"
},
"funding": [
{
@@ -11702,7 +10964,7 @@
"type": "tidelift"
}
],
- "time": "2024-04-28T10:28:08+00:00"
+ "time": "2024-05-31T14:49:08+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",
@@ -12144,30 +11406,31 @@
},
{
"name": "zbateson/mail-mime-parser",
- "version": "2.4.1",
+ "version": "3.0.1",
"source": {
"type": "git",
"url": "https://github.com/zbateson/mail-mime-parser.git",
- "reference": "ff49e02f6489b38f7cc3d1bd3971adc0f872569c"
+ "reference": "6ade63b0a43047935791d7977e22717a68cc388b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/zbateson/mail-mime-parser/zipball/ff49e02f6489b38f7cc3d1bd3971adc0f872569c",
- "reference": "ff49e02f6489b38f7cc3d1bd3971adc0f872569c",
+ "url": "https://api.github.com/repos/zbateson/mail-mime-parser/zipball/6ade63b0a43047935791d7977e22717a68cc388b",
+ "reference": "6ade63b0a43047935791d7977e22717a68cc388b",
"shasum": ""
},
"require": {
- "guzzlehttp/psr7": "^1.7.0|^2.0",
- "php": ">=7.1",
- "pimple/pimple": "^3.0",
- "zbateson/mb-wrapper": "^1.0.1",
- "zbateson/stream-decorators": "^1.0.6"
+ "guzzlehttp/psr7": "^2.5",
+ "php": ">=8.0",
+ "php-di/php-di": "^6.0|^7.0",
+ "psr/log": "^1|^2|^3",
+ "zbateson/mb-wrapper": "^2.0",
+ "zbateson/stream-decorators": "^2.1"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "*",
- "mikey179/vfsstream": "^1.6.0",
+ "monolog/monolog": "^2|^3",
"phpstan/phpstan": "*",
- "phpunit/phpunit": "<10"
+ "phpunit/phpunit": "^9.6"
},
"suggest": {
"ext-iconv": "For best support/performance",
@@ -12215,24 +11478,24 @@
"type": "github"
}
],
- "time": "2024-04-28T00:58:54+00:00"
+ "time": "2024-04-29T21:53:01+00:00"
},
{
"name": "zbateson/mb-wrapper",
- "version": "1.2.1",
+ "version": "2.0.0",
"source": {
"type": "git",
"url": "https://github.com/zbateson/mb-wrapper.git",
- "reference": "09a8b77eb94af3823a9a6623dcc94f8d988da67f"
+ "reference": "9e4373a153585d12b6c621ac4a6bb143264d4619"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/zbateson/mb-wrapper/zipball/09a8b77eb94af3823a9a6623dcc94f8d988da67f",
- "reference": "09a8b77eb94af3823a9a6623dcc94f8d988da67f",
+ "url": "https://api.github.com/repos/zbateson/mb-wrapper/zipball/9e4373a153585d12b6c621ac4a6bb143264d4619",
+ "reference": "9e4373a153585d12b6c621ac4a6bb143264d4619",
"shasum": ""
},
"require": {
- "php": ">=7.1",
+ "php": ">=8.0",
"symfony/polyfill-iconv": "^1.9",
"symfony/polyfill-mbstring": "^1.9"
},
@@ -12276,7 +11539,7 @@
],
"support": {
"issues": "https://github.com/zbateson/mb-wrapper/issues",
- "source": "https://github.com/zbateson/mb-wrapper/tree/1.2.1"
+ "source": "https://github.com/zbateson/mb-wrapper/tree/2.0.0"
},
"funding": [
{
@@ -12284,31 +11547,31 @@
"type": "github"
}
],
- "time": "2024-03-18T04:31:04+00:00"
+ "time": "2024-03-20T01:38:07+00:00"
},
{
"name": "zbateson/stream-decorators",
- "version": "1.2.1",
+ "version": "2.1.1",
"source": {
"type": "git",
"url": "https://github.com/zbateson/stream-decorators.git",
- "reference": "783b034024fda8eafa19675fb2552f8654d3a3e9"
+ "reference": "32a2a62fb0f26313395c996ebd658d33c3f9c4e5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/zbateson/stream-decorators/zipball/783b034024fda8eafa19675fb2552f8654d3a3e9",
- "reference": "783b034024fda8eafa19675fb2552f8654d3a3e9",
+ "url": "https://api.github.com/repos/zbateson/stream-decorators/zipball/32a2a62fb0f26313395c996ebd658d33c3f9c4e5",
+ "reference": "32a2a62fb0f26313395c996ebd658d33c3f9c4e5",
"shasum": ""
},
"require": {
- "guzzlehttp/psr7": "^1.9 | ^2.0",
- "php": ">=7.2",
- "zbateson/mb-wrapper": "^1.0.0"
+ "guzzlehttp/psr7": "^2.5",
+ "php": ">=8.0",
+ "zbateson/mb-wrapper": "^2.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "*",
"phpstan/phpstan": "*",
- "phpunit/phpunit": "<10.0"
+ "phpunit/phpunit": "^9.6|^10.0"
},
"type": "library",
"autoload": {
@@ -12339,7 +11602,7 @@
],
"support": {
"issues": "https://github.com/zbateson/stream-decorators/issues",
- "source": "https://github.com/zbateson/stream-decorators/tree/1.2.1"
+ "source": "https://github.com/zbateson/stream-decorators/tree/2.1.1"
},
"funding": [
{
@@ -12347,7 +11610,88 @@
"type": "github"
}
],
- "time": "2023-05-30T22:51:52+00:00"
+ "time": "2024-04-29T21:42:39+00:00"
+ },
+ {
+ "name": "zircote/swagger-php",
+ "version": "4.10.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/zircote/swagger-php.git",
+ "reference": "ad3f913d39b2a4dfb6e59ee4babb35a6b4a2b998"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/zircote/swagger-php/zipball/ad3f913d39b2a4dfb6e59ee4babb35a6b4a2b998",
+ "reference": "ad3f913d39b2a4dfb6e59ee4babb35a6b4a2b998",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "php": ">=7.2",
+ "psr/log": "^1.1 || ^2.0 || ^3.0",
+ "symfony/deprecation-contracts": "^2 || ^3",
+ "symfony/finder": ">=2.2",
+ "symfony/yaml": ">=3.3"
+ },
+ "require-dev": {
+ "composer/package-versions-deprecated": "^1.11",
+ "doctrine/annotations": "^1.7 || ^2.0",
+ "friendsofphp/php-cs-fixer": "^2.17 || ^3.47.1",
+ "phpstan/phpstan": "^1.6",
+ "phpunit/phpunit": ">=8",
+ "vimeo/psalm": "^4.23"
+ },
+ "suggest": {
+ "doctrine/annotations": "^1.7 || ^2.0"
+ },
+ "bin": [
+ "bin/openapi"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "OpenApi\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Robert Allen",
+ "email": "zircote@gmail.com"
+ },
+ {
+ "name": "Bob Fanger",
+ "email": "bfanger@gmail.com",
+ "homepage": "https://bfanger.nl"
+ },
+ {
+ "name": "Martin Rademacher",
+ "email": "mano@radebatz.net",
+ "homepage": "https://radebatz.net"
+ }
+ ],
+ "description": "swagger-php - Generate interactive documentation for your RESTful API using phpdoc annotations",
+ "homepage": "https://github.com/zircote/swagger-php/",
+ "keywords": [
+ "api",
+ "json",
+ "rest",
+ "service discovery"
+ ],
+ "support": {
+ "issues": "https://github.com/zircote/swagger-php/issues",
+ "source": "https://github.com/zircote/swagger-php/tree/4.10.3"
+ },
+ "time": "2024-07-04T07:53:11+00:00"
}
],
"packages-dev": [
@@ -12693,47 +12037,43 @@
},
{
"name": "laravel/dusk",
- "version": "v7.13.0",
+ "version": "v8.2.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/dusk.git",
- "reference": "dce7c4cc1c308bb18e95b2b3bf7d06d3f040a1f6"
+ "reference": "f2c0957aa4fbb4a78394e77b8caf969903f28050"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/dusk/zipball/dce7c4cc1c308bb18e95b2b3bf7d06d3f040a1f6",
- "reference": "dce7c4cc1c308bb18e95b2b3bf7d06d3f040a1f6",
+ "url": "https://api.github.com/repos/laravel/dusk/zipball/f2c0957aa4fbb4a78394e77b8caf969903f28050",
+ "reference": "f2c0957aa4fbb4a78394e77b8caf969903f28050",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-zip": "*",
- "guzzlehttp/guzzle": "^7.2",
- "illuminate/console": "^9.0|^10.0",
- "illuminate/support": "^9.0|^10.0",
- "nesbot/carbon": "^2.0",
- "php": "^8.0",
+ "guzzlehttp/guzzle": "^7.5",
+ "illuminate/console": "^10.0|^11.0",
+ "illuminate/support": "^10.0|^11.0",
+ "php": "^8.1",
"php-webdriver/webdriver": "^1.9.0",
- "symfony/console": "^6.0",
- "symfony/finder": "^6.0",
- "symfony/process": "^6.0",
+ "symfony/console": "^6.2|^7.0",
+ "symfony/finder": "^6.2|^7.0",
+ "symfony/process": "^6.2|^7.0",
"vlucas/phpdotenv": "^5.2"
},
"require-dev": {
- "mockery/mockery": "^1.4.2",
- "orchestra/testbench": "^7.33|^8.13",
+ "mockery/mockery": "^1.6",
+ "orchestra/testbench": "^8.19|^9.0",
"phpstan/phpstan": "^1.10",
- "phpunit/phpunit": "^9.5.10|^10.0.1",
- "psy/psysh": "^0.11.12"
+ "phpunit/phpunit": "^10.1|^11.0",
+ "psy/psysh": "^0.11.12|^0.12"
},
"suggest": {
"ext-pcntl": "Used to gracefully terminate Dusk when tests are running."
},
"type": "library",
"extra": {
- "branch-alias": {
- "dev-master": "7.x-dev"
- },
"laravel": {
"providers": [
"Laravel\\Dusk\\DuskServiceProvider"
@@ -12763,22 +12103,22 @@
],
"support": {
"issues": "https://github.com/laravel/dusk/issues",
- "source": "https://github.com/laravel/dusk/tree/v7.13.0"
+ "source": "https://github.com/laravel/dusk/tree/v8.2.1"
},
- "time": "2024-02-23T22:29:53+00:00"
+ "time": "2024-07-08T06:42:12+00:00"
},
{
"name": "laravel/pint",
- "version": "v1.16.0",
+ "version": "v1.16.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
- "reference": "1b3a3dc5bc6a81ff52828ba7277621f1d49d6d98"
+ "reference": "51f1ba679a6afe0315621ad143d788bd7ded0eca"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/laravel/pint/zipball/1b3a3dc5bc6a81ff52828ba7277621f1d49d6d98",
- "reference": "1b3a3dc5bc6a81ff52828ba7277621f1d49d6d98",
+ "url": "https://api.github.com/repos/laravel/pint/zipball/51f1ba679a6afe0315621ad143d788bd7ded0eca",
+ "reference": "51f1ba679a6afe0315621ad143d788bd7ded0eca",
"shasum": ""
},
"require": {
@@ -12789,13 +12129,13 @@
"php": "^8.1.0"
},
"require-dev": {
- "friendsofphp/php-cs-fixer": "^3.57.1",
- "illuminate/view": "^10.48.10",
- "larastan/larastan": "^2.9.6",
+ "friendsofphp/php-cs-fixer": "^3.59.3",
+ "illuminate/view": "^10.48.12",
+ "larastan/larastan": "^2.9.7",
"laravel-zero/framework": "^10.4.0",
"mockery/mockery": "^1.6.12",
"nunomaduro/termwind": "^1.15.1",
- "pestphp/pest": "^2.34.7"
+ "pestphp/pest": "^2.34.8"
},
"bin": [
"builds/pint"
@@ -12831,7 +12171,7 @@
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
- "time": "2024-05-21T18:08:25+00:00"
+ "time": "2024-07-09T15:58:08+00:00"
},
{
"name": "mockery/mockery",
@@ -12918,16 +12258,16 @@
},
{
"name": "myclabs/deep-copy",
- "version": "1.11.1",
+ "version": "1.12.0",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
- "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c"
+ "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
- "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c",
+ "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c",
"shasum": ""
},
"require": {
@@ -12935,11 +12275,12 @@
},
"conflict": {
"doctrine/collections": "<1.6.8",
- "doctrine/common": "<2.13.3 || >=3,<3.2.2"
+ "doctrine/common": "<2.13.3 || >=3 <3.2.2"
},
"require-dev": {
"doctrine/collections": "^1.6.8",
"doctrine/common": "^2.13.3 || ^3.2.2",
+ "phpspec/prophecy": "^1.10",
"phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
},
"type": "library",
@@ -12965,7 +12306,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
- "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1"
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0"
},
"funding": [
{
@@ -12973,44 +12314,42 @@
"type": "tidelift"
}
],
- "time": "2023-03-08T13:26:56+00:00"
+ "time": "2024-06-12T14:39:25+00:00"
},
{
"name": "nunomaduro/collision",
- "version": "v7.10.0",
+ "version": "v8.1.1",
"source": {
"type": "git",
"url": "https://github.com/nunomaduro/collision.git",
- "reference": "49ec67fa7b002712da8526678abd651c09f375b2"
+ "reference": "13e5d538b95a744d85f447a321ce10adb28e9af9"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nunomaduro/collision/zipball/49ec67fa7b002712da8526678abd651c09f375b2",
- "reference": "49ec67fa7b002712da8526678abd651c09f375b2",
+ "url": "https://api.github.com/repos/nunomaduro/collision/zipball/13e5d538b95a744d85f447a321ce10adb28e9af9",
+ "reference": "13e5d538b95a744d85f447a321ce10adb28e9af9",
"shasum": ""
},
"require": {
- "filp/whoops": "^2.15.3",
- "nunomaduro/termwind": "^1.15.1",
- "php": "^8.1.0",
- "symfony/console": "^6.3.4"
+ "filp/whoops": "^2.15.4",
+ "nunomaduro/termwind": "^2.0.1",
+ "php": "^8.2.0",
+ "symfony/console": "^7.0.4"
},
"conflict": {
- "laravel/framework": ">=11.0.0"
+ "laravel/framework": "<11.0.0 || >=12.0.0",
+ "phpunit/phpunit": "<10.5.1 || >=12.0.0"
},
"require-dev": {
- "brianium/paratest": "^7.3.0",
- "laravel/framework": "^10.28.0",
- "laravel/pint": "^1.13.3",
- "laravel/sail": "^1.25.0",
- "laravel/sanctum": "^3.3.1",
- "laravel/tinker": "^2.8.2",
- "nunomaduro/larastan": "^2.6.4",
- "orchestra/testbench-core": "^8.13.0",
- "pestphp/pest": "^2.23.2",
- "phpunit/phpunit": "^10.4.1",
- "sebastian/environment": "^6.0.1",
- "spatie/laravel-ignition": "^2.3.1"
+ "larastan/larastan": "^2.9.2",
+ "laravel/framework": "^11.0.0",
+ "laravel/pint": "^1.14.0",
+ "laravel/sail": "^1.28.2",
+ "laravel/sanctum": "^4.0.0",
+ "laravel/tinker": "^2.9.0",
+ "orchestra/testbench-core": "^9.0.0",
+ "pestphp/pest": "^2.34.1 || ^3.0.0",
+ "sebastian/environment": "^6.0.1 || ^7.0.0"
},
"type": "library",
"extra": {
@@ -13018,6 +12357,9 @@
"providers": [
"NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider"
]
+ },
+ "branch-alias": {
+ "dev-8.x": "8.x-dev"
}
},
"autoload": {
@@ -13069,20 +12411,20 @@
"type": "patreon"
}
],
- "time": "2023-10-11T15:45:01+00:00"
+ "time": "2024-03-06T16:20:09+00:00"
},
{
"name": "pestphp/pest",
- "version": "v2.34.7",
+ "version": "v2.34.8",
"source": {
"type": "git",
"url": "https://github.com/pestphp/pest.git",
- "reference": "a7a3e4240e341d0fee1c54814ce18adc26ce5a76"
+ "reference": "e8f122bf47585c06431e0056189ec6bfd6f41f57"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/pestphp/pest/zipball/a7a3e4240e341d0fee1c54814ce18adc26ce5a76",
- "reference": "a7a3e4240e341d0fee1c54814ce18adc26ce5a76",
+ "url": "https://api.github.com/repos/pestphp/pest/zipball/e8f122bf47585c06431e0056189ec6bfd6f41f57",
+ "reference": "e8f122bf47585c06431e0056189ec6bfd6f41f57",
"shasum": ""
},
"require": {
@@ -13101,8 +12443,8 @@
},
"require-dev": {
"pestphp/pest-dev-tools": "^2.16.0",
- "pestphp/pest-plugin-type-coverage": "^2.8.1",
- "symfony/process": "^6.4.0|^7.0.4"
+ "pestphp/pest-plugin-type-coverage": "^2.8.3",
+ "symfony/process": "^6.4.0|^7.1.1"
},
"bin": [
"bin/pest"
@@ -13165,7 +12507,7 @@
],
"support": {
"issues": "https://github.com/pestphp/pest/issues",
- "source": "https://github.com/pestphp/pest/tree/v2.34.7"
+ "source": "https://github.com/pestphp/pest/tree/v2.34.8"
},
"funding": [
{
@@ -13177,7 +12519,7 @@
"type": "github"
}
],
- "time": "2024-04-05T07:44:17+00:00"
+ "time": "2024-06-10T22:02:16+00:00"
},
{
"name": "pestphp/pest-plugin",
@@ -13570,16 +12912,16 @@
},
{
"name": "phpunit/php-code-coverage",
- "version": "10.1.14",
+ "version": "10.1.15",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "e3f51450ebffe8e0efdf7346ae966a656f7d5e5b"
+ "reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/e3f51450ebffe8e0efdf7346ae966a656f7d5e5b",
- "reference": "e3f51450ebffe8e0efdf7346ae966a656f7d5e5b",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae",
+ "reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae",
"shasum": ""
},
"require": {
@@ -13636,7 +12978,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
- "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.14"
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.15"
},
"funding": [
{
@@ -13644,7 +12986,7 @@
"type": "github"
}
],
- "time": "2024-03-12T15:33:41+00:00"
+ "time": "2024-06-29T08:25:15+00:00"
},
{
"name": "phpunit/php-file-iterator",
@@ -14952,23 +14294,97 @@
"time": "2022-05-20T15:13:10+00:00"
},
{
- "name": "spatie/flare-client-php",
- "version": "1.6.0",
+ "name": "spatie/error-solutions",
+ "version": "1.0.5",
"source": {
"type": "git",
- "url": "https://github.com/spatie/flare-client-php.git",
- "reference": "220a7c8745e9fa427d54099f47147c4b97fe6462"
+ "url": "https://github.com/spatie/error-solutions.git",
+ "reference": "4bb6c734dc992b2db3e26df1ef021c75d2218b13"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/220a7c8745e9fa427d54099f47147c4b97fe6462",
- "reference": "220a7c8745e9fa427d54099f47147c4b97fe6462",
+ "url": "https://api.github.com/repos/spatie/error-solutions/zipball/4bb6c734dc992b2db3e26df1ef021c75d2218b13",
+ "reference": "4bb6c734dc992b2db3e26df1ef021c75d2218b13",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.0"
+ },
+ "require-dev": {
+ "illuminate/broadcasting": "^10.0|^11.0",
+ "illuminate/cache": "^10.0|^11.0",
+ "illuminate/support": "^10.0|^11.0",
+ "livewire/livewire": "^2.11|^3.3.5",
+ "openai-php/client": "^0.10.1",
+ "orchestra/testbench": "^7.0|8.22.3|^9.0",
+ "pestphp/pest": "^2.20",
+ "phpstan/phpstan": "^1.11",
+ "psr/simple-cache": "^3.0",
+ "psr/simple-cache-implementation": "^3.0",
+ "spatie/ray": "^1.28",
+ "symfony/cache": "^5.4|^6.0|^7.0",
+ "symfony/process": "^5.4|^6.0|^7.0",
+ "vlucas/phpdotenv": "^5.5"
+ },
+ "suggest": {
+ "openai-php/client": "Require get solutions from OpenAI",
+ "simple-cache-implementation": "To cache solutions from OpenAI"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\Ignition\\": "legacy/ignition",
+ "Spatie\\ErrorSolutions\\": "src",
+ "Spatie\\LaravelIgnition\\": "legacy/laravel-ignition"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ruben Van Assche",
+ "email": "ruben@spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "This is my package error-solutions",
+ "homepage": "https://github.com/spatie/error-solutions",
+ "keywords": [
+ "error-solutions",
+ "spatie"
+ ],
+ "support": {
+ "issues": "https://github.com/spatie/error-solutions/issues",
+ "source": "https://github.com/spatie/error-solutions/tree/1.0.5"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-09T12:13:32+00:00"
+ },
+ {
+ "name": "spatie/flare-client-php",
+ "version": "1.7.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/flare-client-php.git",
+ "reference": "097040ff51e660e0f6fc863684ac4b02c93fa234"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/097040ff51e660e0f6fc863684ac4b02c93fa234",
+ "reference": "097040ff51e660e0f6fc863684ac4b02c93fa234",
"shasum": ""
},
"require": {
"illuminate/pipeline": "^8.0|^9.0|^10.0|^11.0",
"php": "^8.0",
- "spatie/backtrace": "^1.5.2",
+ "spatie/backtrace": "^1.6.1",
"symfony/http-foundation": "^5.2|^6.0|^7.0",
"symfony/mime": "^5.2|^6.0|^7.0",
"symfony/process": "^5.2|^6.0|^7.0",
@@ -15010,7 +14426,7 @@
],
"support": {
"issues": "https://github.com/spatie/flare-client-php/issues",
- "source": "https://github.com/spatie/flare-client-php/tree/1.6.0"
+ "source": "https://github.com/spatie/flare-client-php/tree/1.7.0"
},
"funding": [
{
@@ -15018,28 +14434,28 @@
"type": "github"
}
],
- "time": "2024-05-22T09:45:39+00:00"
+ "time": "2024-06-12T14:39:14+00:00"
},
{
"name": "spatie/ignition",
- "version": "1.14.1",
+ "version": "1.15.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/ignition.git",
- "reference": "c23cc018c5f423d2f413b99f84655fceb6549811"
+ "reference": "e3a68e137371e1eb9edc7f78ffa733f3b98991d2"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/spatie/ignition/zipball/c23cc018c5f423d2f413b99f84655fceb6549811",
- "reference": "c23cc018c5f423d2f413b99f84655fceb6549811",
+ "url": "https://api.github.com/repos/spatie/ignition/zipball/e3a68e137371e1eb9edc7f78ffa733f3b98991d2",
+ "reference": "e3a68e137371e1eb9edc7f78ffa733f3b98991d2",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-mbstring": "*",
"php": "^8.0",
- "spatie/backtrace": "^1.5.3",
- "spatie/flare-client-php": "^1.4.0",
+ "spatie/error-solutions": "^1.0",
+ "spatie/flare-client-php": "^1.7",
"symfony/console": "^5.4|^6.0|^7.0",
"symfony/var-dumper": "^5.4|^6.0|^7.0"
},
@@ -15101,20 +14517,20 @@
"type": "github"
}
],
- "time": "2024-05-03T15:56:16+00:00"
+ "time": "2024-06-12T14:55:22+00:00"
},
{
"name": "spatie/laravel-ignition",
- "version": "2.7.0",
+ "version": "2.8.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-ignition.git",
- "reference": "f52124d50122611e8a40f628cef5c19ff6cc5b57"
+ "reference": "3c067b75bfb50574db8f7e2c3978c65eed71126c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/f52124d50122611e8a40f628cef5c19ff6cc5b57",
- "reference": "f52124d50122611e8a40f628cef5c19ff6cc5b57",
+ "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/3c067b75bfb50574db8f7e2c3978c65eed71126c",
+ "reference": "3c067b75bfb50574db8f7e2c3978c65eed71126c",
"shasum": ""
},
"require": {
@@ -15123,8 +14539,7 @@
"ext-mbstring": "*",
"illuminate/support": "^10.0|^11.0",
"php": "^8.1",
- "spatie/flare-client-php": "^1.5",
- "spatie/ignition": "^1.14",
+ "spatie/ignition": "^1.15",
"symfony/console": "^6.2.3|^7.0",
"symfony/var-dumper": "^6.2.3|^7.0"
},
@@ -15193,7 +14608,178 @@
"type": "github"
}
],
- "time": "2024-05-02T13:42:49+00:00"
+ "time": "2024-06-12T15:01:18+00:00"
+ },
+ {
+ "name": "symfony/http-client",
+ "version": "v6.4.9",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-client.git",
+ "reference": "6e9db0025db565bcf8f1d46ed734b549e51e6045"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-client/zipball/6e9db0025db565bcf8f1d46ed734b549e51e6045",
+ "reference": "6e9db0025db565bcf8f1d46ed734b549e51e6045",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/log": "^1|^2|^3",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/http-client-contracts": "^3.4.1",
+ "symfony/service-contracts": "^2.5|^3"
+ },
+ "conflict": {
+ "php-http/discovery": "<1.15",
+ "symfony/http-foundation": "<6.3"
+ },
+ "provide": {
+ "php-http/async-client-implementation": "*",
+ "php-http/client-implementation": "*",
+ "psr/http-client-implementation": "1.0",
+ "symfony/http-client-implementation": "3.0"
+ },
+ "require-dev": {
+ "amphp/amp": "^2.5",
+ "amphp/http-client": "^4.2.1",
+ "amphp/http-tunnel": "^1.0",
+ "amphp/socket": "^1.1",
+ "guzzlehttp/promises": "^1.4|^2.0",
+ "nyholm/psr7": "^1.0",
+ "php-http/httplug": "^1.0|^2.0",
+ "psr/http-client": "^1.0",
+ "symfony/dependency-injection": "^5.4|^6.0|^7.0",
+ "symfony/http-kernel": "^5.4|^6.0|^7.0",
+ "symfony/messenger": "^5.4|^6.0|^7.0",
+ "symfony/process": "^5.4|^6.0|^7.0",
+ "symfony/stopwatch": "^5.4|^6.0|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\HttpClient\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "http"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/http-client/tree/v6.4.9"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-06-28T07:59:05+00:00"
+ },
+ {
+ "name": "symfony/http-client-contracts",
+ "version": "v3.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-client-contracts.git",
+ "reference": "20414d96f391677bf80078aa55baece78b82647d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/20414d96f391677bf80078aa55baece78b82647d",
+ "reference": "20414d96f391677bf80078aa55baece78b82647d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.5-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\HttpClient\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Test/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to HTTP clients",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-04-18T09:32:20+00:00"
},
{
"name": "ta-tikoma/phpunit-architecture-test",
diff --git a/config/sanctum.php b/config/sanctum.php
index 529cfdc99..f1e5fc0e5 100644
--- a/config/sanctum.php
+++ b/config/sanctum.php
@@ -60,8 +60,9 @@ return [
*/
'middleware' => [
- 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
- 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class,
+ 'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
+ 'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
+ 'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
],
];
diff --git a/config/sentry.php b/config/sentry.php
index ec3f7ae60..3ce5dfbc9 100644
--- a/config/sentry.php
+++ b/config/sentry.php
@@ -7,7 +7,7 @@ return [
// The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
- 'release' => '4.0.0-beta.307',
+ 'release' => '4.0.0-beta.308',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),
diff --git a/config/version.php b/config/version.php
index ac5a388b8..4e1a16799 100644
--- a/config/version.php
+++ b/config/version.php
@@ -1,3 +1,3 @@
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/database/migrations/2024_07_05_120217_remove_unique_from_tag_names.php b/database/migrations/2024_07_05_120217_remove_unique_from_tag_names.php
new file mode 100644
index 000000000..301de814b
--- /dev/null
+++ b/database/migrations/2024_07_05_120217_remove_unique_from_tag_names.php
@@ -0,0 +1,28 @@
+dropUnique(['name']);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('tags', function (Blueprint $table) {
+ $table->unique(['name']);
+ });
+ }
+};
diff --git a/docker/dev/etc/s6-overlay/s6-rc.d/init-setup/up b/docker/dev/etc/s6-overlay/s6-rc.d/init-setup/up
index e974e54cc..e02307e49 100644
--- a/docker/dev/etc/s6-overlay/s6-rc.d/init-setup/up
+++ b/docker/dev/etc/s6-overlay/s6-rc.d/init-setup/up
@@ -1,5 +1,5 @@
#!/command/execlineb -P
foreground { composer -d /var/www/html/ install }
foreground { php /var/www/html/artisan migrate --step }
-foreground { php /var/www/html/artisan dev:init }
+foreground { php /var/www/html/artisan dev --init }
diff --git a/lang/zh-tw.json b/lang/zh-tw.json
new file mode 100644
index 000000000..63956f7a1
--- /dev/null
+++ b/lang/zh-tw.json
@@ -0,0 +1,30 @@
+{
+ "auth.login": "登入",
+ "auth.login.azure": "使用 Microsoft 登入",
+ "auth.login.bitbucket": "使用 Bitbucket 登入",
+ "auth.login.github": "使用 GitHub 登入",
+ "auth.login.gitlab": "使用 Gitlab 登入",
+ "auth.login.google": "使用 Google 登入",
+ "auth.already_registered": "已經註冊?",
+ "auth.confirm_password": "確認密碼",
+ "auth.forgot_password": "忘記密碼",
+ "auth.forgot_password_send_email": "發送重設密碼電郵",
+ "auth.register_now": "註冊",
+ "auth.logout": "登出",
+ "auth.register": "註冊",
+ "auth.registration_disabled": "註冊已停用,請聯絡管理員。",
+ "auth.reset_password": "重設密碼",
+ "auth.failed": "這些憑證與我們的記錄不符。",
+ "auth.failed.callback": "無法處理來自登入提供者的回呼。",
+ "auth.failed.password": "密碼錯誤。",
+ "auth.failed.email": "找不到該電子郵件地址的使用者。",
+ "auth.throttle": "登入嘗試次數太多。請在 :seconds 秒後重試。",
+ "input.name": "名稱",
+ "input.email": "電子郵件",
+ "input.password": "密碼",
+ "input.password.again": "再次輸入密碼",
+ "input.code": "一次性代碼",
+ "input.recovery_code": "恢復碼",
+ "button.save": "儲存",
+ "repository.url": "例子
對於公共代碼倉庫,請使用 https://...。
對於私有代碼倉庫,請使用 git@...。
https://github.com/coollabsio/coolify-examples main 分支將被選擇
https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify nodejs-fastify 分支將被選擇。
https://gitea.com/sedlav/expressjs.git main 分支將被選擇。
https://gitlab.com/andrasbacsai/nodejs-example.git main 分支將被選擇。"
+}
diff --git a/openapi.yaml b/openapi.yaml
new file mode 100644
index 000000000..94e26ce38
--- /dev/null
+++ b/openapi.yaml
@@ -0,0 +1,4721 @@
+openapi: 3.0.0
+info:
+ title: Coolify
+ version: '0.1'
+servers:
+ -
+ url: 'https://app.coolify.io/api/v1'
+paths:
+ /applications:
+ get:
+ tags:
+ - Applications
+ summary: List
+ description: 'List all applications.'
+ operationId: 02978e79fc0b54d573b2359f2a1f7d86
+ responses:
+ '200':
+ description: 'Get all applications.'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Application'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ patch:
+ tags:
+ - Applications
+ summary: Update
+ description: 'Update application by UUID.'
+ operationId: ff28a22d25b1f658c40b54d2073abbca
+ requestBody:
+ description: 'Application updated.'
+ required: true
+ content:
+ application/json:
+ schema:
+ properties:
+ project_uuid:
+ type: string
+ description: 'The project UUID.'
+ server_uuid:
+ type: string
+ description: 'The server UUID.'
+ environment_name:
+ type: string
+ description: 'The environment name.'
+ github_app_uuid:
+ type: string
+ description: 'The Github App UUID.'
+ git_repository:
+ type: string
+ description: 'The git repository URL.'
+ git_branch:
+ type: string
+ description: 'The git branch.'
+ ports_exposes:
+ type: string
+ description: 'The ports to expose.'
+ destination_uuid:
+ type: string
+ description: 'The destination UUID.'
+ build_pack:
+ type: string
+ enum: [nixpacks, static, dockerfile, dockercompose]
+ description: 'The build pack type.'
+ name:
+ type: string
+ description: 'The application name.'
+ description:
+ type: string
+ description: 'The application description.'
+ domains:
+ type: string
+ description: 'The application domains.'
+ git_commit_sha:
+ type: string
+ description: 'The git commit SHA.'
+ docker_registry_image_name:
+ type: string
+ description: 'The docker registry image name.'
+ docker_registry_image_tag:
+ type: string
+ description: 'The docker registry image tag.'
+ is_static:
+ type: boolean
+ description: 'The flag to indicate if the application is static.'
+ install_command:
+ type: string
+ description: 'The install command.'
+ build_command:
+ type: string
+ description: 'The build command.'
+ start_command:
+ type: string
+ description: 'The start command.'
+ ports_mappings:
+ type: string
+ description: 'The ports mappings.'
+ base_directory:
+ type: string
+ description: 'The base directory for all commands.'
+ publish_directory:
+ type: string
+ description: 'The publish directory.'
+ health_check_enabled:
+ type: boolean
+ description: 'Health check enabled.'
+ health_check_path:
+ type: string
+ description: 'Health check path.'
+ health_check_port:
+ type: string
+ nullable: true
+ description: 'Health check port.'
+ health_check_host:
+ type: string
+ nullable: true
+ description: 'Health check host.'
+ health_check_method:
+ type: string
+ description: 'Health check method.'
+ health_check_return_code:
+ type: integer
+ description: 'Health check return code.'
+ health_check_scheme:
+ type: string
+ description: 'Health check scheme.'
+ health_check_response_text:
+ type: string
+ nullable: true
+ description: 'Health check response text.'
+ health_check_interval:
+ type: integer
+ description: 'Health check interval in seconds.'
+ health_check_timeout:
+ type: integer
+ description: 'Health check timeout in seconds.'
+ health_check_retries:
+ type: integer
+ description: 'Health check retries count.'
+ health_check_start_period:
+ type: integer
+ description: 'Health check start period in seconds.'
+ limits_memory:
+ type: string
+ description: 'Memory limit.'
+ limits_memory_swap:
+ type: string
+ description: 'Memory swap limit.'
+ limits_memory_swappiness:
+ type: integer
+ description: 'Memory swappiness.'
+ limits_memory_reservation:
+ type: string
+ description: 'Memory reservation.'
+ limits_cpus:
+ type: string
+ description: 'CPU limit.'
+ limits_cpuset:
+ type: string
+ nullable: true
+ description: 'CPU set.'
+ limits_cpu_shares:
+ type: integer
+ description: 'CPU shares.'
+ custom_labels:
+ type: string
+ description: 'Custom labels.'
+ custom_docker_run_options:
+ type: string
+ description: 'Custom docker run options.'
+ post_deployment_command:
+ type: string
+ description: 'Post deployment command.'
+ post_deployment_command_container:
+ type: string
+ description: 'Post deployment command container.'
+ pre_deployment_command:
+ type: string
+ description: 'Pre deployment command.'
+ pre_deployment_command_container:
+ type: string
+ description: 'Pre deployment command container.'
+ manual_webhook_secret_github:
+ type: string
+ description: 'Manual webhook secret for Github.'
+ manual_webhook_secret_gitlab:
+ type: string
+ description: 'Manual webhook secret for Gitlab.'
+ manual_webhook_secret_bitbucket:
+ type: string
+ description: 'Manual webhook secret for Bitbucket.'
+ manual_webhook_secret_gitea:
+ type: string
+ description: 'Manual webhook secret for Gitea.'
+ redirect:
+ type: string
+ nullable: true
+ description: 'How to set redirect with Traefik / Caddy. www<->non-www.'
+ enum: [www, non-www, both]
+ instant_deploy:
+ type: boolean
+ description: 'The flag to indicate if the application should be deployed instantly.'
+ dockerfile:
+ type: string
+ description: 'The Dockerfile content.'
+ docker_compose_location:
+ type: string
+ description: 'The Docker Compose location.'
+ docker_compose_raw:
+ type: string
+ description: 'The Docker Compose raw content.'
+ docker_compose_custom_start_command:
+ type: string
+ description: 'The Docker Compose custom start command.'
+ docker_compose_custom_build_command:
+ type: string
+ description: 'The Docker Compose custom build command.'
+ docker_compose_domains:
+ type: array
+ description: 'The Docker Compose domains.'
+ watch_paths:
+ type: string
+ description: 'The watch paths.'
+ type: object
+ responses:
+ '200':
+ description: 'Application updated.'
+ content:
+ application/json:
+ schema:
+ properties:
+ uuid: { type: string }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ /applications/public:
+ post:
+ tags:
+ - Applications
+ summary: 'Create (Public)'
+ description: 'Create new application based on a public git repository.'
+ operationId: cb56324ad19693469b4461d3f6065a5b
+ requestBody:
+ description: 'Application object that needs to be created.'
+ required: true
+ content:
+ application/json:
+ schema:
+ required:
+ - project_uuid
+ - server_uuid
+ - environment_name
+ - git_repository
+ - git_branch
+ - build_pack
+ - ports_exposes
+ properties:
+ project_uuid:
+ type: string
+ description: 'The project UUID.'
+ server_uuid:
+ type: string
+ description: 'The server UUID.'
+ environment_name:
+ type: string
+ description: 'The environment name.'
+ git_repository:
+ type: string
+ description: 'The git repository URL.'
+ git_branch:
+ type: string
+ description: 'The git branch.'
+ build_pack:
+ type: string
+ enum: [nixpacks, static, dockerfile, dockercompose]
+ description: 'The build pack type.'
+ ports_exposes:
+ type: string
+ description: 'The ports to expose.'
+ destination_uuid:
+ type: string
+ description: 'The destination UUID.'
+ name:
+ type: string
+ description: 'The application name.'
+ description:
+ type: string
+ description: 'The application description.'
+ domains:
+ type: string
+ description: 'The application domains.'
+ git_commit_sha:
+ type: string
+ description: 'The git commit SHA.'
+ docker_registry_image_name:
+ type: string
+ description: 'The docker registry image name.'
+ docker_registry_image_tag:
+ type: string
+ description: 'The docker registry image tag.'
+ is_static:
+ type: boolean
+ description: 'The flag to indicate if the application is static.'
+ install_command:
+ type: string
+ description: 'The install command.'
+ build_command:
+ type: string
+ description: 'The build command.'
+ start_command:
+ type: string
+ description: 'The start command.'
+ ports_mappings:
+ type: string
+ description: 'The ports mappings.'
+ base_directory:
+ type: string
+ description: 'The base directory for all commands.'
+ publish_directory:
+ type: string
+ description: 'The publish directory.'
+ health_check_enabled:
+ type: boolean
+ description: 'Health check enabled.'
+ health_check_path:
+ type: string
+ description: 'Health check path.'
+ health_check_port:
+ type: string
+ nullable: true
+ description: 'Health check port.'
+ health_check_host:
+ type: string
+ nullable: true
+ description: 'Health check host.'
+ health_check_method:
+ type: string
+ description: 'Health check method.'
+ health_check_return_code:
+ type: integer
+ description: 'Health check return code.'
+ health_check_scheme:
+ type: string
+ description: 'Health check scheme.'
+ health_check_response_text:
+ type: string
+ nullable: true
+ description: 'Health check response text.'
+ health_check_interval:
+ type: integer
+ description: 'Health check interval in seconds.'
+ health_check_timeout:
+ type: integer
+ description: 'Health check timeout in seconds.'
+ health_check_retries:
+ type: integer
+ description: 'Health check retries count.'
+ health_check_start_period:
+ type: integer
+ description: 'Health check start period in seconds.'
+ limits_memory:
+ type: string
+ description: 'Memory limit.'
+ limits_memory_swap:
+ type: string
+ description: 'Memory swap limit.'
+ limits_memory_swappiness:
+ type: integer
+ description: 'Memory swappiness.'
+ limits_memory_reservation:
+ type: string
+ description: 'Memory reservation.'
+ limits_cpus:
+ type: string
+ description: 'CPU limit.'
+ limits_cpuset:
+ type: string
+ nullable: true
+ description: 'CPU set.'
+ limits_cpu_shares:
+ type: integer
+ description: 'CPU shares.'
+ custom_labels:
+ type: string
+ description: 'Custom labels.'
+ custom_docker_run_options:
+ type: string
+ description: 'Custom docker run options.'
+ post_deployment_command:
+ type: string
+ description: 'Post deployment command.'
+ post_deployment_command_container:
+ type: string
+ description: 'Post deployment command container.'
+ pre_deployment_command:
+ type: string
+ description: 'Pre deployment command.'
+ pre_deployment_command_container:
+ type: string
+ description: 'Pre deployment command container.'
+ manual_webhook_secret_github:
+ type: string
+ description: 'Manual webhook secret for Github.'
+ manual_webhook_secret_gitlab:
+ type: string
+ description: 'Manual webhook secret for Gitlab.'
+ manual_webhook_secret_bitbucket:
+ type: string
+ description: 'Manual webhook secret for Bitbucket.'
+ manual_webhook_secret_gitea:
+ type: string
+ description: 'Manual webhook secret for Gitea.'
+ redirect:
+ type: string
+ nullable: true
+ description: 'How to set redirect with Traefik / Caddy. www<->non-www.'
+ enum: [www, non-www, both]
+ instant_deploy:
+ type: boolean
+ description: 'The flag to indicate if the application should be deployed instantly.'
+ dockerfile:
+ type: string
+ description: 'The Dockerfile content.'
+ docker_compose_location:
+ type: string
+ description: 'The Docker Compose location.'
+ docker_compose_raw:
+ type: string
+ description: 'The Docker Compose raw content.'
+ docker_compose_custom_start_command:
+ type: string
+ description: 'The Docker Compose custom start command.'
+ docker_compose_custom_build_command:
+ type: string
+ description: 'The Docker Compose custom build command.'
+ docker_compose_domains:
+ type: array
+ description: 'The Docker Compose domains.'
+ watch_paths:
+ type: string
+ description: 'The watch paths.'
+ type: object
+ responses:
+ '200':
+ description: 'Application created successfully.'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ /applications/private-gh-app:
+ post:
+ tags:
+ - Applications
+ summary: 'Create (Private - GH App)'
+ description: 'Create new application based on a private repository through a Github App.'
+ operationId: 4d46c84bda4f1a411f6dda15fce4061f
+ requestBody:
+ description: 'Application object that needs to be created.'
+ required: true
+ content:
+ application/json:
+ schema:
+ required:
+ - project_uuid
+ - server_uuid
+ - environment_name
+ - github_app_uuid
+ - git_repository
+ - git_branch
+ - build_pack
+ - ports_exposes
+ properties:
+ project_uuid:
+ type: string
+ description: 'The project UUID.'
+ server_uuid:
+ type: string
+ description: 'The server UUID.'
+ environment_name:
+ type: string
+ description: 'The environment name.'
+ github_app_uuid:
+ type: string
+ description: 'The Github App UUID.'
+ git_repository:
+ type: string
+ description: 'The git repository URL.'
+ git_branch:
+ type: string
+ description: 'The git branch.'
+ ports_exposes:
+ type: string
+ description: 'The ports to expose.'
+ destination_uuid:
+ type: string
+ description: 'The destination UUID.'
+ build_pack:
+ type: string
+ enum: [nixpacks, static, dockerfile, dockercompose]
+ description: 'The build pack type.'
+ name:
+ type: string
+ description: 'The application name.'
+ description:
+ type: string
+ description: 'The application description.'
+ domains:
+ type: string
+ description: 'The application domains.'
+ git_commit_sha:
+ type: string
+ description: 'The git commit SHA.'
+ docker_registry_image_name:
+ type: string
+ description: 'The docker registry image name.'
+ docker_registry_image_tag:
+ type: string
+ description: 'The docker registry image tag.'
+ is_static:
+ type: boolean
+ description: 'The flag to indicate if the application is static.'
+ install_command:
+ type: string
+ description: 'The install command.'
+ build_command:
+ type: string
+ description: 'The build command.'
+ start_command:
+ type: string
+ description: 'The start command.'
+ ports_mappings:
+ type: string
+ description: 'The ports mappings.'
+ base_directory:
+ type: string
+ description: 'The base directory for all commands.'
+ publish_directory:
+ type: string
+ description: 'The publish directory.'
+ health_check_enabled:
+ type: boolean
+ description: 'Health check enabled.'
+ health_check_path:
+ type: string
+ description: 'Health check path.'
+ health_check_port:
+ type: string
+ nullable: true
+ description: 'Health check port.'
+ health_check_host:
+ type: string
+ nullable: true
+ description: 'Health check host.'
+ health_check_method:
+ type: string
+ description: 'Health check method.'
+ health_check_return_code:
+ type: integer
+ description: 'Health check return code.'
+ health_check_scheme:
+ type: string
+ description: 'Health check scheme.'
+ health_check_response_text:
+ type: string
+ nullable: true
+ description: 'Health check response text.'
+ health_check_interval:
+ type: integer
+ description: 'Health check interval in seconds.'
+ health_check_timeout:
+ type: integer
+ description: 'Health check timeout in seconds.'
+ health_check_retries:
+ type: integer
+ description: 'Health check retries count.'
+ health_check_start_period:
+ type: integer
+ description: 'Health check start period in seconds.'
+ limits_memory:
+ type: string
+ description: 'Memory limit.'
+ limits_memory_swap:
+ type: string
+ description: 'Memory swap limit.'
+ limits_memory_swappiness:
+ type: integer
+ description: 'Memory swappiness.'
+ limits_memory_reservation:
+ type: string
+ description: 'Memory reservation.'
+ limits_cpus:
+ type: string
+ description: 'CPU limit.'
+ limits_cpuset:
+ type: string
+ nullable: true
+ description: 'CPU set.'
+ limits_cpu_shares:
+ type: integer
+ description: 'CPU shares.'
+ custom_labels:
+ type: string
+ description: 'Custom labels.'
+ custom_docker_run_options:
+ type: string
+ description: 'Custom docker run options.'
+ post_deployment_command:
+ type: string
+ description: 'Post deployment command.'
+ post_deployment_command_container:
+ type: string
+ description: 'Post deployment command container.'
+ pre_deployment_command:
+ type: string
+ description: 'Pre deployment command.'
+ pre_deployment_command_container:
+ type: string
+ description: 'Pre deployment command container.'
+ manual_webhook_secret_github:
+ type: string
+ description: 'Manual webhook secret for Github.'
+ manual_webhook_secret_gitlab:
+ type: string
+ description: 'Manual webhook secret for Gitlab.'
+ manual_webhook_secret_bitbucket:
+ type: string
+ description: 'Manual webhook secret for Bitbucket.'
+ manual_webhook_secret_gitea:
+ type: string
+ description: 'Manual webhook secret for Gitea.'
+ redirect:
+ type: string
+ nullable: true
+ description: 'How to set redirect with Traefik / Caddy. www<->non-www.'
+ enum: [www, non-www, both]
+ instant_deploy:
+ type: boolean
+ description: 'The flag to indicate if the application should be deployed instantly.'
+ dockerfile:
+ type: string
+ description: 'The Dockerfile content.'
+ docker_compose_location:
+ type: string
+ description: 'The Docker Compose location.'
+ docker_compose_raw:
+ type: string
+ description: 'The Docker Compose raw content.'
+ docker_compose_custom_start_command:
+ type: string
+ description: 'The Docker Compose custom start command.'
+ docker_compose_custom_build_command:
+ type: string
+ description: 'The Docker Compose custom build command.'
+ docker_compose_domains:
+ type: array
+ description: 'The Docker Compose domains.'
+ watch_paths:
+ type: string
+ description: 'The watch paths.'
+ type: object
+ responses:
+ '200':
+ description: 'Application created successfully.'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ /applications/private-deploy-key:
+ post:
+ tags:
+ - Applications
+ summary: 'Create (Private - Deploy Key)'
+ description: 'Create new application based on a private repository through a Deploy Key.'
+ operationId: e3eaa989ffb05366247a00cdfd551efa
+ requestBody:
+ description: 'Application object that needs to be created.'
+ required: true
+ content:
+ application/json:
+ schema:
+ required:
+ - project_uuid
+ - server_uuid
+ - environment_name
+ - private_key_uuid
+ - git_repository
+ - git_branch
+ - build_pack
+ - ports_exposes
+ properties:
+ project_uuid:
+ type: string
+ description: 'The project UUID.'
+ server_uuid:
+ type: string
+ description: 'The server UUID.'
+ environment_name:
+ type: string
+ description: 'The environment name.'
+ private_key_uuid:
+ type: string
+ description: 'The private key UUID.'
+ git_repository:
+ type: string
+ description: 'The git repository URL.'
+ git_branch:
+ type: string
+ description: 'The git branch.'
+ ports_exposes:
+ type: string
+ description: 'The ports to expose.'
+ destination_uuid:
+ type: string
+ description: 'The destination UUID.'
+ build_pack:
+ type: string
+ enum: [nixpacks, static, dockerfile, dockercompose]
+ description: 'The build pack type.'
+ name:
+ type: string
+ description: 'The application name.'
+ description:
+ type: string
+ description: 'The application description.'
+ domains:
+ type: string
+ description: 'The application domains.'
+ git_commit_sha:
+ type: string
+ description: 'The git commit SHA.'
+ docker_registry_image_name:
+ type: string
+ description: 'The docker registry image name.'
+ docker_registry_image_tag:
+ type: string
+ description: 'The docker registry image tag.'
+ is_static:
+ type: boolean
+ description: 'The flag to indicate if the application is static.'
+ install_command:
+ type: string
+ description: 'The install command.'
+ build_command:
+ type: string
+ description: 'The build command.'
+ start_command:
+ type: string
+ description: 'The start command.'
+ ports_mappings:
+ type: string
+ description: 'The ports mappings.'
+ base_directory:
+ type: string
+ description: 'The base directory for all commands.'
+ publish_directory:
+ type: string
+ description: 'The publish directory.'
+ health_check_enabled:
+ type: boolean
+ description: 'Health check enabled.'
+ health_check_path:
+ type: string
+ description: 'Health check path.'
+ health_check_port:
+ type: string
+ nullable: true
+ description: 'Health check port.'
+ health_check_host:
+ type: string
+ nullable: true
+ description: 'Health check host.'
+ health_check_method:
+ type: string
+ description: 'Health check method.'
+ health_check_return_code:
+ type: integer
+ description: 'Health check return code.'
+ health_check_scheme:
+ type: string
+ description: 'Health check scheme.'
+ health_check_response_text:
+ type: string
+ nullable: true
+ description: 'Health check response text.'
+ health_check_interval:
+ type: integer
+ description: 'Health check interval in seconds.'
+ health_check_timeout:
+ type: integer
+ description: 'Health check timeout in seconds.'
+ health_check_retries:
+ type: integer
+ description: 'Health check retries count.'
+ health_check_start_period:
+ type: integer
+ description: 'Health check start period in seconds.'
+ limits_memory:
+ type: string
+ description: 'Memory limit.'
+ limits_memory_swap:
+ type: string
+ description: 'Memory swap limit.'
+ limits_memory_swappiness:
+ type: integer
+ description: 'Memory swappiness.'
+ limits_memory_reservation:
+ type: string
+ description: 'Memory reservation.'
+ limits_cpus:
+ type: string
+ description: 'CPU limit.'
+ limits_cpuset:
+ type: string
+ nullable: true
+ description: 'CPU set.'
+ limits_cpu_shares:
+ type: integer
+ description: 'CPU shares.'
+ custom_labels:
+ type: string
+ description: 'Custom labels.'
+ custom_docker_run_options:
+ type: string
+ description: 'Custom docker run options.'
+ post_deployment_command:
+ type: string
+ description: 'Post deployment command.'
+ post_deployment_command_container:
+ type: string
+ description: 'Post deployment command container.'
+ pre_deployment_command:
+ type: string
+ description: 'Pre deployment command.'
+ pre_deployment_command_container:
+ type: string
+ description: 'Pre deployment command container.'
+ manual_webhook_secret_github:
+ type: string
+ description: 'Manual webhook secret for Github.'
+ manual_webhook_secret_gitlab:
+ type: string
+ description: 'Manual webhook secret for Gitlab.'
+ manual_webhook_secret_bitbucket:
+ type: string
+ description: 'Manual webhook secret for Bitbucket.'
+ manual_webhook_secret_gitea:
+ type: string
+ description: 'Manual webhook secret for Gitea.'
+ redirect:
+ type: string
+ nullable: true
+ description: 'How to set redirect with Traefik / Caddy. www<->non-www.'
+ enum: [www, non-www, both]
+ instant_deploy:
+ type: boolean
+ description: 'The flag to indicate if the application should be deployed instantly.'
+ dockerfile:
+ type: string
+ description: 'The Dockerfile content.'
+ docker_compose_location:
+ type: string
+ description: 'The Docker Compose location.'
+ docker_compose_raw:
+ type: string
+ description: 'The Docker Compose raw content.'
+ docker_compose_custom_start_command:
+ type: string
+ description: 'The Docker Compose custom start command.'
+ docker_compose_custom_build_command:
+ type: string
+ description: 'The Docker Compose custom build command.'
+ docker_compose_domains:
+ type: array
+ description: 'The Docker Compose domains.'
+ watch_paths:
+ type: string
+ description: 'The watch paths.'
+ type: object
+ responses:
+ '200':
+ description: 'Application created successfully.'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ /applications/dockerfile:
+ post:
+ tags:
+ - Applications
+ summary: 'Create (Dockerfile)'
+ description: 'Create new application based on a simple Dockerfile.'
+ operationId: 2b433ad6f5d259eb7f4f3b5af9913708
+ requestBody:
+ description: 'Application object that needs to be created.'
+ required: true
+ content:
+ application/json:
+ schema:
+ required:
+ - project_uuid
+ - server_uuid
+ - environment_name
+ - dockerfile
+ properties:
+ project_uuid:
+ type: string
+ description: 'The project UUID.'
+ server_uuid:
+ type: string
+ description: 'The server UUID.'
+ environment_name:
+ type: string
+ description: 'The environment name.'
+ dockerfile:
+ type: string
+ description: 'The Dockerfile content.'
+ build_pack:
+ type: string
+ enum: [nixpacks, static, dockerfile, dockercompose]
+ description: 'The build pack type.'
+ ports_exposes:
+ type: string
+ description: 'The ports to expose.'
+ destination_uuid:
+ type: string
+ description: 'The destination UUID.'
+ name:
+ type: string
+ description: 'The application name.'
+ description:
+ type: string
+ description: 'The application description.'
+ domains:
+ type: string
+ description: 'The application domains.'
+ docker_registry_image_name:
+ type: string
+ description: 'The docker registry image name.'
+ docker_registry_image_tag:
+ type: string
+ description: 'The docker registry image tag.'
+ ports_mappings:
+ type: string
+ description: 'The ports mappings.'
+ base_directory:
+ type: string
+ description: 'The base directory for all commands.'
+ health_check_enabled:
+ type: boolean
+ description: 'Health check enabled.'
+ health_check_path:
+ type: string
+ description: 'Health check path.'
+ health_check_port:
+ type: string
+ nullable: true
+ description: 'Health check port.'
+ health_check_host:
+ type: string
+ nullable: true
+ description: 'Health check host.'
+ health_check_method:
+ type: string
+ description: 'Health check method.'
+ health_check_return_code:
+ type: integer
+ description: 'Health check return code.'
+ health_check_scheme:
+ type: string
+ description: 'Health check scheme.'
+ health_check_response_text:
+ type: string
+ nullable: true
+ description: 'Health check response text.'
+ health_check_interval:
+ type: integer
+ description: 'Health check interval in seconds.'
+ health_check_timeout:
+ type: integer
+ description: 'Health check timeout in seconds.'
+ health_check_retries:
+ type: integer
+ description: 'Health check retries count.'
+ health_check_start_period:
+ type: integer
+ description: 'Health check start period in seconds.'
+ limits_memory:
+ type: string
+ description: 'Memory limit.'
+ limits_memory_swap:
+ type: string
+ description: 'Memory swap limit.'
+ limits_memory_swappiness:
+ type: integer
+ description: 'Memory swappiness.'
+ limits_memory_reservation:
+ type: string
+ description: 'Memory reservation.'
+ limits_cpus:
+ type: string
+ description: 'CPU limit.'
+ limits_cpuset:
+ type: string
+ nullable: true
+ description: 'CPU set.'
+ limits_cpu_shares:
+ type: integer
+ description: 'CPU shares.'
+ custom_labels:
+ type: string
+ description: 'Custom labels.'
+ custom_docker_run_options:
+ type: string
+ description: 'Custom docker run options.'
+ post_deployment_command:
+ type: string
+ description: 'Post deployment command.'
+ post_deployment_command_container:
+ type: string
+ description: 'Post deployment command container.'
+ pre_deployment_command:
+ type: string
+ description: 'Pre deployment command.'
+ pre_deployment_command_container:
+ type: string
+ description: 'Pre deployment command container.'
+ manual_webhook_secret_github:
+ type: string
+ description: 'Manual webhook secret for Github.'
+ manual_webhook_secret_gitlab:
+ type: string
+ description: 'Manual webhook secret for Gitlab.'
+ manual_webhook_secret_bitbucket:
+ type: string
+ description: 'Manual webhook secret for Bitbucket.'
+ manual_webhook_secret_gitea:
+ type: string
+ description: 'Manual webhook secret for Gitea.'
+ redirect:
+ type: string
+ nullable: true
+ description: 'How to set redirect with Traefik / Caddy. www<->non-www.'
+ enum: [www, non-www, both]
+ instant_deploy:
+ type: boolean
+ description: 'The flag to indicate if the application should be deployed instantly.'
+ type: object
+ responses:
+ '200':
+ description: 'Application created successfully.'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ /applications/dockerimage:
+ post:
+ tags:
+ - Applications
+ summary: 'Create (Docker Image)'
+ description: 'Create new application based on a prebuilt docker image'
+ operationId: e9a2d6dd9404acf880dc3053f09477fc
+ requestBody:
+ description: 'Application object that needs to be created.'
+ required: true
+ content:
+ application/json:
+ schema:
+ required:
+ - project_uuid
+ - server_uuid
+ - environment_name
+ - docker_registry_image_name
+ - ports_exposes
+ properties:
+ project_uuid:
+ type: string
+ description: 'The project UUID.'
+ server_uuid:
+ type: string
+ description: 'The server UUID.'
+ environment_name:
+ type: string
+ description: 'The environment name.'
+ docker_registry_image_name:
+ type: string
+ description: 'The docker registry image name.'
+ docker_registry_image_tag:
+ type: string
+ description: 'The docker registry image tag.'
+ ports_exposes:
+ type: string
+ description: 'The ports to expose.'
+ destination_uuid:
+ type: string
+ description: 'The destination UUID.'
+ name:
+ type: string
+ description: 'The application name.'
+ description:
+ type: string
+ description: 'The application description.'
+ domains:
+ type: string
+ description: 'The application domains.'
+ ports_mappings:
+ type: string
+ description: 'The ports mappings.'
+ health_check_enabled:
+ type: boolean
+ description: 'Health check enabled.'
+ health_check_path:
+ type: string
+ description: 'Health check path.'
+ health_check_port:
+ type: string
+ nullable: true
+ description: 'Health check port.'
+ health_check_host:
+ type: string
+ nullable: true
+ description: 'Health check host.'
+ health_check_method:
+ type: string
+ description: 'Health check method.'
+ health_check_return_code:
+ type: integer
+ description: 'Health check return code.'
+ health_check_scheme:
+ type: string
+ description: 'Health check scheme.'
+ health_check_response_text:
+ type: string
+ nullable: true
+ description: 'Health check response text.'
+ health_check_interval:
+ type: integer
+ description: 'Health check interval in seconds.'
+ health_check_timeout:
+ type: integer
+ description: 'Health check timeout in seconds.'
+ health_check_retries:
+ type: integer
+ description: 'Health check retries count.'
+ health_check_start_period:
+ type: integer
+ description: 'Health check start period in seconds.'
+ limits_memory:
+ type: string
+ description: 'Memory limit.'
+ limits_memory_swap:
+ type: string
+ description: 'Memory swap limit.'
+ limits_memory_swappiness:
+ type: integer
+ description: 'Memory swappiness.'
+ limits_memory_reservation:
+ type: string
+ description: 'Memory reservation.'
+ limits_cpus:
+ type: string
+ description: 'CPU limit.'
+ limits_cpuset:
+ type: string
+ nullable: true
+ description: 'CPU set.'
+ limits_cpu_shares:
+ type: integer
+ description: 'CPU shares.'
+ custom_labels:
+ type: string
+ description: 'Custom labels.'
+ custom_docker_run_options:
+ type: string
+ description: 'Custom docker run options.'
+ post_deployment_command:
+ type: string
+ description: 'Post deployment command.'
+ post_deployment_command_container:
+ type: string
+ description: 'Post deployment command container.'
+ pre_deployment_command:
+ type: string
+ description: 'Pre deployment command.'
+ pre_deployment_command_container:
+ type: string
+ description: 'Pre deployment command container.'
+ manual_webhook_secret_github:
+ type: string
+ description: 'Manual webhook secret for Github.'
+ manual_webhook_secret_gitlab:
+ type: string
+ description: 'Manual webhook secret for Gitlab.'
+ manual_webhook_secret_bitbucket:
+ type: string
+ description: 'Manual webhook secret for Bitbucket.'
+ manual_webhook_secret_gitea:
+ type: string
+ description: 'Manual webhook secret for Gitea.'
+ redirect:
+ type: string
+ nullable: true
+ description: 'How to set redirect with Traefik / Caddy. www<->non-www.'
+ enum: [www, non-www, both]
+ instant_deploy:
+ type: boolean
+ description: 'The flag to indicate if the application should be deployed instantly.'
+ type: object
+ responses:
+ '200':
+ description: 'Application created successfully.'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ /applications/dockercompose:
+ post:
+ tags:
+ - Applications
+ summary: 'Create (Docker Compose)'
+ description: 'Create new application based on a docker-compose file.'
+ operationId: 3731add8226c2d664455978cac46c242
+ requestBody:
+ description: 'Application object that needs to be created.'
+ required: true
+ content:
+ application/json:
+ schema:
+ required:
+ - project_uuid
+ - server_uuid
+ - environment_name
+ - docker_compose_raw
+ properties:
+ project_uuid:
+ type: string
+ description: 'The project UUID.'
+ server_uuid:
+ type: string
+ description: 'The server UUID.'
+ environment_name:
+ type: string
+ description: 'The environment name.'
+ docker_compose_raw:
+ type: string
+ description: 'The Docker Compose raw content.'
+ destination_uuid:
+ type: string
+ description: 'The destination UUID if the server has more than one destinations.'
+ name:
+ type: string
+ description: 'The application name.'
+ description:
+ type: string
+ description: 'The application description.'
+ instant_deploy:
+ type: boolean
+ description: 'The flag to indicate if the application should be deployed instantly.'
+ type: object
+ responses:
+ '200':
+ description: 'Application created successfully.'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ '/applications/{uuid}':
+ get:
+ tags:
+ - Applications
+ summary: Get
+ description: 'Get application by UUID.'
+ operationId: 3630b62c28e7358e7f0087c1d8fe1845
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'UUID of the application.'
+ required: true
+ schema:
+ type: string
+ format: uuid
+ responses:
+ '200':
+ description: 'Get application by UUID.'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Application'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ delete:
+ tags:
+ - Applications
+ summary: Delete
+ description: 'Delete application by UUID.'
+ operationId: 1e110b190a1045d34f3e1c61608a8702
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'UUID of the application.'
+ required: true
+ schema:
+ type: string
+ format: uuid
+ responses:
+ '200':
+ description: 'Application deleted.'
+ content:
+ application/json:
+ schema:
+ properties:
+ message: { type: string, example: 'Application deleted.' }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ '/applications/{uuid}/envs':
+ get:
+ tags:
+ - Applications
+ summary: 'List Envs'
+ description: 'List all envs by application UUID.'
+ operationId: 7c8e0c286870e23294a075cc0584df2f
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'UUID of the application.'
+ required: true
+ schema:
+ type: string
+ format: uuid
+ responses:
+ '200':
+ description: 'All environment variables by application UUID.'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/EnvironmentVariable'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ post:
+ tags:
+ - Applications
+ summary: 'Create Env'
+ description: 'Create env by application UUID.'
+ operationId: 4699ffbb7d6e58581fd0b0a14f36ffc2
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'UUID of the application.'
+ required: true
+ schema:
+ type: string
+ format: uuid
+ requestBody:
+ description: 'Env created.'
+ required: true
+ content:
+ application/json:
+ schema:
+ properties:
+ key:
+ type: string
+ description: 'The key of the environment variable.'
+ value:
+ type: string
+ description: 'The value of the environment variable.'
+ is_preview:
+ type: boolean
+ description: 'The flag to indicate if the environment variable is used in preview deployments.'
+ is_build_time:
+ type: boolean
+ description: 'The flag to indicate if the environment variable is used in build time.'
+ is_literal:
+ type: boolean
+ description: 'The flag to indicate if the environment variable is a literal, nothing espaced.'
+ is_multiline:
+ type: boolean
+ description: 'The flag to indicate if the environment variable is multiline.'
+ is_shown_once:
+ type: boolean
+ description: "The flag to indicate if the environment variable's value is shown on the UI."
+ type: object
+ responses:
+ '201':
+ description: 'Environment variable created.'
+ content:
+ application/json:
+ schema:
+ properties:
+ uuid: { type: string, example: nc0k04gk8g0cgsk440g0koko }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ patch:
+ tags:
+ - Applications
+ summary: 'Update Env'
+ description: 'Update env by application UUID.'
+ operationId: 3d70a2d569f395be220b3f09ad36674b
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'UUID of the application.'
+ required: true
+ schema:
+ type: string
+ format: uuid
+ requestBody:
+ description: 'Env updated.'
+ required: true
+ content:
+ application/json:
+ schema:
+ required:
+ - key
+ - value
+ properties:
+ key:
+ type: string
+ description: 'The key of the environment variable.'
+ value:
+ type: string
+ description: 'The value of the environment variable.'
+ is_preview:
+ type: boolean
+ description: 'The flag to indicate if the environment variable is used in preview deployments.'
+ is_build_time:
+ type: boolean
+ description: 'The flag to indicate if the environment variable is used in build time.'
+ is_literal:
+ type: boolean
+ description: 'The flag to indicate if the environment variable is a literal, nothing espaced.'
+ is_multiline:
+ type: boolean
+ description: 'The flag to indicate if the environment variable is multiline.'
+ is_shown_once:
+ type: boolean
+ description: "The flag to indicate if the environment variable's value is shown on the UI."
+ type: object
+ responses:
+ '201':
+ description: 'Environment variable updated.'
+ content:
+ application/json:
+ schema:
+ properties:
+ message: { type: string, example: 'Environment variable updated.' }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ '/applications/{uuid}/envs/bulk':
+ patch:
+ tags:
+ - Applications
+ summary: 'Update Envs (Bulk)'
+ description: 'Update multiple envs by application UUID.'
+ operationId: ae96f0f585ed158b2abd2d9ba40f3cf9
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'UUID of the application.'
+ required: true
+ schema:
+ type: string
+ format: uuid
+ requestBody:
+ description: 'Bulk envs updated.'
+ required: true
+ content:
+ application/json:
+ schema:
+ required:
+ - data
+ properties:
+ data:
+ type: array
+ items: { properties: { key: { type: string, description: 'The key of the environment variable.' }, value: { type: string, description: 'The value of the environment variable.' }, is_preview: { type: boolean, description: 'The flag to indicate if the environment variable is used in preview deployments.' }, is_build_time: { type: boolean, description: 'The flag to indicate if the environment variable is used in build time.' }, is_literal: { type: boolean, description: 'The flag to indicate if the environment variable is a literal, nothing espaced.' }, is_multiline: { type: boolean, description: 'The flag to indicate if the environment variable is multiline.' }, is_shown_once: { type: boolean, description: "The flag to indicate if the environment variable's value is shown on the UI." } }, type: object }
+ type: object
+ responses:
+ '201':
+ description: 'Environment variables updated.'
+ content:
+ application/json:
+ schema:
+ properties:
+ message: { type: string, example: 'Environment variables updated.' }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ '/applications/{uuid}/envs/{env_uuid}':
+ delete:
+ tags:
+ - Applications
+ summary: 'Delete Env'
+ description: 'Delete env by UUID.'
+ operationId: 96097c5cfc7dc0e7a3de229645f630c7
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'UUID of the application.'
+ required: true
+ schema:
+ type: string
+ format: uuid
+ -
+ name: env_uuid
+ in: path
+ description: 'UUID of the environment variable.'
+ required: true
+ schema:
+ type: string
+ format: uuid
+ responses:
+ '200':
+ description: 'Environment variable deleted.'
+ content:
+ application/json:
+ schema:
+ properties:
+ message: { type: string, example: 'Environment variable deleted.' }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ '/applications/{uuid}/start':
+ get:
+ tags:
+ - Applications
+ summary: Start
+ description: 'Start application. `Post` request is also accepted.'
+ operationId: dc87c2061ab303757a0e061f87900c4c
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'UUID of the application.'
+ required: true
+ schema:
+ type: string
+ format: uuid
+ -
+ name: force
+ in: query
+ description: 'Force rebuild.'
+ schema:
+ type: boolean
+ default: false
+ -
+ name: instant_deploy
+ in: query
+ description: 'Instant deploy (skip queuing).'
+ schema:
+ type: boolean
+ default: false
+ responses:
+ '200':
+ description: 'Start application.'
+ content:
+ application/json:
+ schema:
+ properties:
+ message: { type: string, example: 'Deployment request queued.', description: Message. }
+ deployment_uuid: { type: string, example: doogksw, description: 'UUID of the deployment.' }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ '/applications/{uuid}/stop':
+ get:
+ tags:
+ - Applications
+ summary: Stop
+ description: 'Stop application. `Post` request is also accepted.'
+ operationId: 133ef3c7bd5043901f24bb5002a536eb
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'UUID of the application.'
+ required: true
+ schema:
+ type: string
+ format: uuid
+ responses:
+ '200':
+ description: 'Stop application.'
+ content:
+ application/json:
+ schema:
+ properties:
+ message: { type: string, example: 'Application stopping request queued.' }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ '/applications/{uuid}/restart':
+ get:
+ tags:
+ - Applications
+ summary: Restart
+ description: 'Restart application. `Post` request is also accepted.'
+ operationId: b231ae7baab9ef47f0627be820e735bc
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'UUID of the application.'
+ required: true
+ schema:
+ type: string
+ format: uuid
+ responses:
+ '200':
+ description: 'Restart application.'
+ content:
+ application/json:
+ schema:
+ properties:
+ message: { type: string, example: 'Restart request queued.' }
+ deployment_uuid: { type: string, example: doogksw, description: 'UUID of the deployment.' }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ /databases:
+ get:
+ tags:
+ - Databases
+ summary: List
+ description: 'List all databases.'
+ operationId: ecd0ee1e46e4c854c18e6c9daa3d37f3
+ responses:
+ '200':
+ description: 'Get all databases'
+ content:
+ application/json:
+ schema:
+ type: string
+ example: 'Content is very complex. Will be implemented later.'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ '/databases/{uuid}':
+ get:
+ tags:
+ - Databases
+ summary: Get
+ description: 'Get database by UUID.'
+ operationId: b49cb2d3e8f34c4e80cdffd8a201031d
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'UUID of the database.'
+ required: true
+ schema:
+ type: string
+ format: uuid
+ responses:
+ '200':
+ description: 'Get all databases'
+ content:
+ application/json:
+ schema:
+ type: string
+ example: 'Content is very complex. Will be implemented later.'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ delete:
+ tags:
+ - Databases
+ summary: Delete
+ description: 'Delete database by UUID.'
+ operationId: 20610931b2bae8aba34eee68624ab673
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'UUID of the database.'
+ required: true
+ schema:
+ type: string
+ format: uuid
+ responses:
+ '200':
+ description: 'Database deleted.'
+ content:
+ application/json:
+ schema:
+ properties:
+ message: { type: string, example: 'Database deleted.' }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ patch:
+ tags:
+ - Databases
+ summary: Update
+ description: 'Update database by UUID.'
+ operationId: 5ba459ed390a721711a1708760e9de3b
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'UUID of the database.'
+ required: true
+ schema:
+ type: string
+ format: uuid
+ requestBody:
+ description: 'Database data'
+ required: true
+ content:
+ application/json:
+ schema:
+ properties:
+ name:
+ type: string
+ description: 'Name of the database'
+ description:
+ type: string
+ description: 'Description of the database'
+ image:
+ type: string
+ description: 'Docker Image of the database'
+ is_public:
+ type: boolean
+ description: 'Is the database public?'
+ public_port:
+ type: integer
+ description: 'Public port of the database'
+ limits_memory:
+ type: string
+ description: 'Memory limit of the database'
+ limits_memory_swap:
+ type: string
+ description: 'Memory swap limit of the database'
+ limits_memory_swappiness:
+ type: integer
+ description: 'Memory swappiness of the database'
+ limits_memory_reservation:
+ type: string
+ description: 'Memory reservation of the database'
+ limits_cpus:
+ type: string
+ description: 'CPU limit of the database'
+ limits_cpuset:
+ type: string
+ description: 'CPU set of the database'
+ limits_cpu_shares:
+ type: integer
+ description: 'CPU shares of the database'
+ postgres_user:
+ type: string
+ description: 'PostgreSQL user'
+ postgres_password:
+ type: string
+ description: 'PostgreSQL password'
+ postgres_db:
+ type: string
+ description: 'PostgreSQL database'
+ postgres_initdb_args:
+ type: string
+ description: 'PostgreSQL initdb args'
+ postgres_host_auth_method:
+ type: string
+ description: 'PostgreSQL host auth method'
+ postgres_conf:
+ type: string
+ description: 'PostgreSQL conf'
+ clickhouse_admin_user:
+ type: string
+ description: 'Clickhouse admin user'
+ clickhouse_admin_password:
+ type: string
+ description: 'Clickhouse admin password'
+ dragonfly_password:
+ type: string
+ description: 'DragonFly password'
+ redis_password:
+ type: string
+ description: 'Redis password'
+ redis_conf:
+ type: string
+ description: 'Redis conf'
+ keydb_password:
+ type: string
+ description: 'KeyDB password'
+ keydb_conf:
+ type: string
+ description: 'KeyDB conf'
+ mariadb_conf:
+ type: string
+ description: 'MariaDB conf'
+ mariadb_root_password:
+ type: string
+ description: 'MariaDB root password'
+ mariadb_user:
+ type: string
+ description: 'MariaDB user'
+ mariadb_password:
+ type: string
+ description: 'MariaDB password'
+ mariadb_database:
+ type: string
+ description: 'MariaDB database'
+ mongo_conf:
+ type: string
+ description: 'Mongo conf'
+ mongo_initdb_root_username:
+ type: string
+ description: 'Mongo initdb root username'
+ mongo_initdb_root_password:
+ type: string
+ description: 'Mongo initdb root password'
+ mongo_initdb_init_database:
+ type: string
+ description: 'Mongo initdb init database'
+ mysql_root_password:
+ type: string
+ description: 'MySQL root password'
+ mysql_user:
+ type: string
+ description: 'MySQL user'
+ mysql_database:
+ type: string
+ description: 'MySQL database'
+ mysql_conf:
+ type: string
+ description: 'MySQL conf'
+ type: object
+ responses:
+ '200':
+ description: 'Database updated'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ /databases/postgresql:
+ post:
+ tags:
+ - Databases
+ summary: 'Create (PostgreSQL)'
+ description: 'Create a new PostgreSQL database.'
+ operationId: 8f7f491ddc46a9fa065b4424512231cd
+ requestBody:
+ description: 'Database data'
+ required: true
+ content:
+ application/json:
+ schema:
+ required:
+ - server_uuid
+ - project_uuid
+ - environment_name
+ properties:
+ server_uuid:
+ type: string
+ description: 'UUID of the server'
+ project_uuid:
+ type: string
+ description: 'UUID of the project'
+ environment_name:
+ type: string
+ description: 'Name of the environment'
+ postgres_user:
+ type: string
+ description: 'PostgreSQL user'
+ postgres_password:
+ type: string
+ description: 'PostgreSQL password'
+ postgres_db:
+ type: string
+ description: 'PostgreSQL database'
+ postgres_initdb_args:
+ type: string
+ description: 'PostgreSQL initdb args'
+ postgres_host_auth_method:
+ type: string
+ description: 'PostgreSQL host auth method'
+ postgres_conf:
+ type: string
+ description: 'PostgreSQL conf'
+ destination_uuid:
+ type: string
+ description: 'UUID of the destination if the server has multiple destinations'
+ name:
+ type: string
+ description: 'Name of the database'
+ description:
+ type: string
+ description: 'Description of the database'
+ image:
+ type: string
+ description: 'Docker Image of the database'
+ is_public:
+ type: boolean
+ description: 'Is the database public?'
+ public_port:
+ type: integer
+ description: 'Public port of the database'
+ limits_memory:
+ type: string
+ description: 'Memory limit of the database'
+ limits_memory_swap:
+ type: string
+ description: 'Memory swap limit of the database'
+ limits_memory_swappiness:
+ type: integer
+ description: 'Memory swappiness of the database'
+ limits_memory_reservation:
+ type: string
+ description: 'Memory reservation of the database'
+ limits_cpus:
+ type: string
+ description: 'CPU limit of the database'
+ limits_cpuset:
+ type: string
+ description: 'CPU set of the database'
+ limits_cpu_shares:
+ type: integer
+ description: 'CPU shares of the database'
+ instant_deploy:
+ type: boolean
+ description: 'Instant deploy the database'
+ type: object
+ responses:
+ '200':
+ description: 'Database updated'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ /databases/clickhouse:
+ post:
+ tags:
+ - Databases
+ summary: 'Create (Clickhouse)'
+ description: 'Create a new Clickhouse database.'
+ operationId: a1189fa7f956f238f0e95c9150ff57f6
+ requestBody:
+ description: 'Database data'
+ required: true
+ content:
+ application/json:
+ schema:
+ required:
+ - server_uuid
+ - project_uuid
+ - environment_name
+ properties:
+ server_uuid:
+ type: string
+ description: 'UUID of the server'
+ project_uuid:
+ type: string
+ description: 'UUID of the project'
+ environment_name:
+ type: string
+ description: 'Name of the environment'
+ destination_uuid:
+ type: string
+ description: 'UUID of the destination if the server has multiple destinations'
+ clickhouse_admin_user:
+ type: string
+ description: 'Clickhouse admin user'
+ clickhouse_admin_password:
+ type: string
+ description: 'Clickhouse admin password'
+ name:
+ type: string
+ description: 'Name of the database'
+ description:
+ type: string
+ description: 'Description of the database'
+ image:
+ type: string
+ description: 'Docker Image of the database'
+ is_public:
+ type: boolean
+ description: 'Is the database public?'
+ public_port:
+ type: integer
+ description: 'Public port of the database'
+ limits_memory:
+ type: string
+ description: 'Memory limit of the database'
+ limits_memory_swap:
+ type: string
+ description: 'Memory swap limit of the database'
+ limits_memory_swappiness:
+ type: integer
+ description: 'Memory swappiness of the database'
+ limits_memory_reservation:
+ type: string
+ description: 'Memory reservation of the database'
+ limits_cpus:
+ type: string
+ description: 'CPU limit of the database'
+ limits_cpuset:
+ type: string
+ description: 'CPU set of the database'
+ limits_cpu_shares:
+ type: integer
+ description: 'CPU shares of the database'
+ instant_deploy:
+ type: boolean
+ description: 'Instant deploy the database'
+ type: object
+ responses:
+ '200':
+ description: 'Database updated'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ /databases/dragonfly:
+ post:
+ tags:
+ - Databases
+ summary: 'Create (DragonFly)'
+ description: 'Create a new DragonFly database.'
+ operationId: e73f7de1c8eee4219e5ec98c4b9b7efe
+ requestBody:
+ description: 'Database data'
+ required: true
+ content:
+ application/json:
+ schema:
+ required:
+ - server_uuid
+ - project_uuid
+ - environment_name
+ properties:
+ server_uuid:
+ type: string
+ description: 'UUID of the server'
+ project_uuid:
+ type: string
+ description: 'UUID of the project'
+ environment_name:
+ type: string
+ description: 'Name of the environment'
+ destination_uuid:
+ type: string
+ description: 'UUID of the destination if the server has multiple destinations'
+ dragonfly_password:
+ type: string
+ description: 'DragonFly password'
+ name:
+ type: string
+ description: 'Name of the database'
+ description:
+ type: string
+ description: 'Description of the database'
+ image:
+ type: string
+ description: 'Docker Image of the database'
+ is_public:
+ type: boolean
+ description: 'Is the database public?'
+ public_port:
+ type: integer
+ description: 'Public port of the database'
+ limits_memory:
+ type: string
+ description: 'Memory limit of the database'
+ limits_memory_swap:
+ type: string
+ description: 'Memory swap limit of the database'
+ limits_memory_swappiness:
+ type: integer
+ description: 'Memory swappiness of the database'
+ limits_memory_reservation:
+ type: string
+ description: 'Memory reservation of the database'
+ limits_cpus:
+ type: string
+ description: 'CPU limit of the database'
+ limits_cpuset:
+ type: string
+ description: 'CPU set of the database'
+ limits_cpu_shares:
+ type: integer
+ description: 'CPU shares of the database'
+ instant_deploy:
+ type: boolean
+ description: 'Instant deploy the database'
+ type: object
+ responses:
+ '200':
+ description: 'Database updated'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ /databases/redis:
+ post:
+ tags:
+ - Databases
+ summary: 'Create (Redis)'
+ description: 'Create a new Redis database.'
+ operationId: 4d352d13544ee2953fd48ad7b0651098
+ requestBody:
+ description: 'Database data'
+ required: true
+ content:
+ application/json:
+ schema:
+ required:
+ - server_uuid
+ - project_uuid
+ - environment_name
+ properties:
+ server_uuid:
+ type: string
+ description: 'UUID of the server'
+ project_uuid:
+ type: string
+ description: 'UUID of the project'
+ environment_name:
+ type: string
+ description: 'Name of the environment'
+ destination_uuid:
+ type: string
+ description: 'UUID of the destination if the server has multiple destinations'
+ redis_password:
+ type: string
+ description: 'Redis password'
+ redis_conf:
+ type: string
+ description: 'Redis conf'
+ name:
+ type: string
+ description: 'Name of the database'
+ description:
+ type: string
+ description: 'Description of the database'
+ image:
+ type: string
+ description: 'Docker Image of the database'
+ is_public:
+ type: boolean
+ description: 'Is the database public?'
+ public_port:
+ type: integer
+ description: 'Public port of the database'
+ limits_memory:
+ type: string
+ description: 'Memory limit of the database'
+ limits_memory_swap:
+ type: string
+ description: 'Memory swap limit of the database'
+ limits_memory_swappiness:
+ type: integer
+ description: 'Memory swappiness of the database'
+ limits_memory_reservation:
+ type: string
+ description: 'Memory reservation of the database'
+ limits_cpus:
+ type: string
+ description: 'CPU limit of the database'
+ limits_cpuset:
+ type: string
+ description: 'CPU set of the database'
+ limits_cpu_shares:
+ type: integer
+ description: 'CPU shares of the database'
+ instant_deploy:
+ type: boolean
+ description: 'Instant deploy the database'
+ type: object
+ responses:
+ '200':
+ description: 'Database updated'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ /databases/keydb:
+ post:
+ tags:
+ - Databases
+ summary: 'Create (KeyDB)'
+ description: 'Create a new KeyDB database.'
+ operationId: b908f3929c371c217d489638e0a21ff6
+ requestBody:
+ description: 'Database data'
+ required: true
+ content:
+ application/json:
+ schema:
+ required:
+ - server_uuid
+ - project_uuid
+ - environment_name
+ properties:
+ server_uuid:
+ type: string
+ description: 'UUID of the server'
+ project_uuid:
+ type: string
+ description: 'UUID of the project'
+ environment_name:
+ type: string
+ description: 'Name of the environment'
+ destination_uuid:
+ type: string
+ description: 'UUID of the destination if the server has multiple destinations'
+ keydb_password:
+ type: string
+ description: 'KeyDB password'
+ keydb_conf:
+ type: string
+ description: 'KeyDB conf'
+ name:
+ type: string
+ description: 'Name of the database'
+ description:
+ type: string
+ description: 'Description of the database'
+ image:
+ type: string
+ description: 'Docker Image of the database'
+ is_public:
+ type: boolean
+ description: 'Is the database public?'
+ public_port:
+ type: integer
+ description: 'Public port of the database'
+ limits_memory:
+ type: string
+ description: 'Memory limit of the database'
+ limits_memory_swap:
+ type: string
+ description: 'Memory swap limit of the database'
+ limits_memory_swappiness:
+ type: integer
+ description: 'Memory swappiness of the database'
+ limits_memory_reservation:
+ type: string
+ description: 'Memory reservation of the database'
+ limits_cpus:
+ type: string
+ description: 'CPU limit of the database'
+ limits_cpuset:
+ type: string
+ description: 'CPU set of the database'
+ limits_cpu_shares:
+ type: integer
+ description: 'CPU shares of the database'
+ instant_deploy:
+ type: boolean
+ description: 'Instant deploy the database'
+ type: object
+ responses:
+ '200':
+ description: 'Database updated'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ /databases/mariadb:
+ post:
+ tags:
+ - Databases
+ summary: 'Create (MariaDB)'
+ description: 'Create a new MariaDB database.'
+ operationId: 6bea521ddcd738dcbb5f3783a7308acf
+ requestBody:
+ description: 'Database data'
+ required: true
+ content:
+ application/json:
+ schema:
+ required:
+ - server_uuid
+ - project_uuid
+ - environment_name
+ properties:
+ server_uuid:
+ type: string
+ description: 'UUID of the server'
+ project_uuid:
+ type: string
+ description: 'UUID of the project'
+ environment_name:
+ type: string
+ description: 'Name of the environment'
+ destination_uuid:
+ type: string
+ description: 'UUID of the destination if the server has multiple destinations'
+ mariadb_conf:
+ type: string
+ description: 'MariaDB conf'
+ mariadb_root_password:
+ type: string
+ description: 'MariaDB root password'
+ mariadb_user:
+ type: string
+ description: 'MariaDB user'
+ mariadb_password:
+ type: string
+ description: 'MariaDB password'
+ mariadb_database:
+ type: string
+ description: 'MariaDB database'
+ name:
+ type: string
+ description: 'Name of the database'
+ description:
+ type: string
+ description: 'Description of the database'
+ image:
+ type: string
+ description: 'Docker Image of the database'
+ is_public:
+ type: boolean
+ description: 'Is the database public?'
+ public_port:
+ type: integer
+ description: 'Public port of the database'
+ limits_memory:
+ type: string
+ description: 'Memory limit of the database'
+ limits_memory_swap:
+ type: string
+ description: 'Memory swap limit of the database'
+ limits_memory_swappiness:
+ type: integer
+ description: 'Memory swappiness of the database'
+ limits_memory_reservation:
+ type: string
+ description: 'Memory reservation of the database'
+ limits_cpus:
+ type: string
+ description: 'CPU limit of the database'
+ limits_cpuset:
+ type: string
+ description: 'CPU set of the database'
+ limits_cpu_shares:
+ type: integer
+ description: 'CPU shares of the database'
+ instant_deploy:
+ type: boolean
+ description: 'Instant deploy the database'
+ type: object
+ responses:
+ '200':
+ description: 'Database updated'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ /databases/mysql:
+ post:
+ tags:
+ - Databases
+ summary: 'Create (MySQL)'
+ description: 'Create a new MySQL database.'
+ operationId: 0a1158cf759c4493cbb1e30024c60623
+ requestBody:
+ description: 'Database data'
+ required: true
+ content:
+ application/json:
+ schema:
+ required:
+ - server_uuid
+ - project_uuid
+ - environment_name
+ properties:
+ server_uuid:
+ type: string
+ description: 'UUID of the server'
+ project_uuid:
+ type: string
+ description: 'UUID of the project'
+ environment_name:
+ type: string
+ description: 'Name of the environment'
+ destination_uuid:
+ type: string
+ description: 'UUID of the destination if the server has multiple destinations'
+ mysql_root_password:
+ type: string
+ description: 'MySQL root password'
+ mysql_user:
+ type: string
+ description: 'MySQL user'
+ mysql_database:
+ type: string
+ description: 'MySQL database'
+ mysql_conf:
+ type: string
+ description: 'MySQL conf'
+ name:
+ type: string
+ description: 'Name of the database'
+ description:
+ type: string
+ description: 'Description of the database'
+ image:
+ type: string
+ description: 'Docker Image of the database'
+ is_public:
+ type: boolean
+ description: 'Is the database public?'
+ public_port:
+ type: integer
+ description: 'Public port of the database'
+ limits_memory:
+ type: string
+ description: 'Memory limit of the database'
+ limits_memory_swap:
+ type: string
+ description: 'Memory swap limit of the database'
+ limits_memory_swappiness:
+ type: integer
+ description: 'Memory swappiness of the database'
+ limits_memory_reservation:
+ type: string
+ description: 'Memory reservation of the database'
+ limits_cpus:
+ type: string
+ description: 'CPU limit of the database'
+ limits_cpuset:
+ type: string
+ description: 'CPU set of the database'
+ limits_cpu_shares:
+ type: integer
+ description: 'CPU shares of the database'
+ instant_deploy:
+ type: boolean
+ description: 'Instant deploy the database'
+ type: object
+ responses:
+ '200':
+ description: 'Database updated'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ /databases/mongodb:
+ post:
+ tags:
+ - Databases
+ summary: 'Create (MongoDB)'
+ description: 'Create a new MongoDB database.'
+ operationId: fdba3de84d02519bb37599fea34b115d
+ requestBody:
+ description: 'Database data'
+ required: true
+ content:
+ application/json:
+ schema:
+ required:
+ - server_uuid
+ - project_uuid
+ - environment_name
+ properties:
+ server_uuid:
+ type: string
+ description: 'UUID of the server'
+ project_uuid:
+ type: string
+ description: 'UUID of the project'
+ environment_name:
+ type: string
+ description: 'Name of the environment'
+ destination_uuid:
+ type: string
+ description: 'UUID of the destination if the server has multiple destinations'
+ mongo_conf:
+ type: string
+ description: 'MongoDB conf'
+ mongo_initdb_root_username:
+ type: string
+ description: 'MongoDB initdb root username'
+ name:
+ type: string
+ description: 'Name of the database'
+ description:
+ type: string
+ description: 'Description of the database'
+ image:
+ type: string
+ description: 'Docker Image of the database'
+ is_public:
+ type: boolean
+ description: 'Is the database public?'
+ public_port:
+ type: integer
+ description: 'Public port of the database'
+ limits_memory:
+ type: string
+ description: 'Memory limit of the database'
+ limits_memory_swap:
+ type: string
+ description: 'Memory swap limit of the database'
+ limits_memory_swappiness:
+ type: integer
+ description: 'Memory swappiness of the database'
+ limits_memory_reservation:
+ type: string
+ description: 'Memory reservation of the database'
+ limits_cpus:
+ type: string
+ description: 'CPU limit of the database'
+ limits_cpuset:
+ type: string
+ description: 'CPU set of the database'
+ limits_cpu_shares:
+ type: integer
+ description: 'CPU shares of the database'
+ instant_deploy:
+ type: boolean
+ description: 'Instant deploy the database'
+ type: object
+ responses:
+ '200':
+ description: 'Database updated'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ '/databases/{uuid}/start':
+ get:
+ tags:
+ - Databases
+ summary: Start
+ description: 'Start database. `Post` request is also accepted.'
+ operationId: 4c6eb21e734d411e2b3388578761123d
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'UUID of the database.'
+ required: true
+ schema:
+ type: string
+ format: uuid
+ responses:
+ '200':
+ description: 'Start database.'
+ content:
+ application/json:
+ schema:
+ properties:
+ message: { type: string, example: 'Database starting request queued.' }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ '/databases/{uuid}/stop':
+ get:
+ tags:
+ - Databases
+ summary: Stop
+ description: 'Stop database. `Post` request is also accepted.'
+ operationId: cb6d983c2679aff841c7501ce612a372
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'UUID of the database.'
+ required: true
+ schema:
+ type: string
+ format: uuid
+ responses:
+ '200':
+ description: 'Stop database.'
+ content:
+ application/json:
+ schema:
+ properties:
+ message: { type: string, example: 'Database stopping request queued.' }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ '/databases/{uuid}/restart':
+ get:
+ tags:
+ - Databases
+ summary: Restart
+ description: 'Restart database. `Post` request is also accepted.'
+ operationId: 04c7a5e4752b4a00036addb433f3f218
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'UUID of the database.'
+ required: true
+ schema:
+ type: string
+ format: uuid
+ responses:
+ '200':
+ description: 'Restart database.'
+ content:
+ application/json:
+ schema:
+ properties:
+ message: { type: string, example: 'Database restaring request queued.' }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ /deployments:
+ get:
+ tags:
+ - Deployments
+ summary: List
+ description: 'List currently running deployments'
+ operationId: a2c05736269191ad0d99cadfd4708986
+ responses:
+ '200':
+ description: 'Get all currently running deployments.'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/ApplicationDeploymentQueue'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ '/deployments/{uuid}':
+ get:
+ tags:
+ - Deployments
+ summary: Get
+ description: 'Get deployment by UUID.'
+ operationId: ccf9856174c115a1430d952ccbd36aea
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'Deployment Uuid'
+ required: true
+ schema:
+ type: integer
+ responses:
+ '200':
+ description: 'Get deployment by UUID.'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ApplicationDeploymentQueue'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ /deploy:
+ get:
+ tags:
+ - Deployments
+ summary: Deploy
+ description: 'Deploy by tag or uuid. `Post` request also accepted.'
+ operationId: 700eb6e51f4c9e86d722f600c65ed1d4
+ parameters:
+ -
+ name: tag
+ in: query
+ description: 'Tag name(s). Comma separated list is also accepted.'
+ schema:
+ type: string
+ -
+ name: uuid
+ in: query
+ description: 'Resource UUID(s). Comma separated list is also accepted.'
+ schema:
+ type: string
+ -
+ name: force
+ in: query
+ description: 'Force rebuild (without cache)'
+ schema:
+ type: boolean
+ responses:
+ '200':
+ description: "Get deployment(s) Uuid's"
+ content:
+ application/json:
+ schema:
+ properties:
+ deployments: { type: array, items: { properties: { message: { type: string }, resource_uuid: { type: string }, deployment_uuid: { type: string } }, type: object } }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ /version:
+ get:
+ summary: Version
+ description: 'Get Coolify version.'
+ operationId: 187b37139844731110757711ee71c215
+ responses:
+ '200':
+ description: 'Returns the version of the application'
+ content:
+ application/json:
+ schema:
+ type: string
+ example: v4.0.0
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ /enable:
+ get:
+ summary: 'Enable API'
+ description: 'Enable API (only with root permissions).'
+ operationId: 595019bae03d08277def667609779ff3
+ responses:
+ '200':
+ description: 'Enable API.'
+ content:
+ application/json:
+ schema:
+ properties:
+ message: { type: string, example: 'API enabled.' }
+ type: object
+ '403':
+ description: 'You are not allowed to enable the API.'
+ content:
+ application/json:
+ schema:
+ properties:
+ message: { type: string, example: 'You are not allowed to enable the API.' }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ /disable:
+ get:
+ summary: 'Disable API'
+ description: 'Disable API (only with root permissions).'
+ operationId: 50e2486a2d196a996b24a284a283bcdb
+ responses:
+ '200':
+ description: 'Disable API.'
+ content:
+ application/json:
+ schema:
+ properties:
+ message: { type: string, example: 'API disabled.' }
+ type: object
+ '403':
+ description: 'You are not allowed to disable the API.'
+ content:
+ application/json:
+ schema:
+ properties:
+ message: { type: string, example: 'You are not allowed to disable the API.' }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ /healthcheck:
+ get:
+ summary: Healthcheck
+ description: 'Healthcheck endpoint.'
+ operationId: 64db893135e686704bb88c3c238022c1
+ responses:
+ '200':
+ description: 'Healthcheck endpoint.'
+ content:
+ application/json:
+ schema:
+ type: string
+ example: OK
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ /projects:
+ get:
+ tags:
+ - Projects
+ summary: List
+ description: 'list projects.'
+ operationId: 762788f00f2dabb981df9adbc948d3f6
+ responses:
+ '200':
+ description: 'Get all projects.'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Project'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ '/projects/{uuid}':
+ get:
+ tags:
+ - Projects
+ summary: Get
+ description: 'Get project by Uuid.'
+ operationId: 63bf8b6a68fbb757f09ab519331f6298
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'Project UUID'
+ required: true
+ schema:
+ type: integer
+ responses:
+ '200':
+ description: 'Project details'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Project'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ description: 'Project not found.'
+ security:
+ -
+ bearerAuth: []
+ '/projects/{uuid}/{environment_name}':
+ get:
+ tags:
+ - Projects
+ summary: Environment
+ description: 'Get environment by name.'
+ operationId: 7e44845dce5aa47ed7b0daf5595ad2e1
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'Project UUID'
+ required: true
+ schema:
+ type: integer
+ -
+ name: environment_name
+ in: path
+ description: 'Environment name'
+ required: true
+ schema:
+ type: string
+ responses:
+ '200':
+ description: 'Project details'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Environment'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ /resources:
+ get:
+ tags:
+ - Resources
+ summary: List
+ description: 'Get all resources.'
+ operationId: c399903694eb1314596832e49f7c66d7
+ responses:
+ '200':
+ description: 'Get all resources'
+ content:
+ application/json:
+ schema:
+ type: string
+ example: 'Content is very complex. Will be implemented later.'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ /security/keys:
+ get:
+ tags:
+ - 'Private Keys'
+ summary: List
+ description: 'List all private keys.'
+ operationId: 8a5d8d3ccbbcef54ed0e913a27faea9d
+ responses:
+ '200':
+ description: 'Get all private keys.'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/PrivateKey'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ post:
+ tags:
+ - 'Private Keys'
+ summary: Create
+ description: 'Create a new private key.'
+ operationId: eb4780acaa990c594cdbe8ffa80b4fb0
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ required:
+ - private_key
+ properties:
+ name:
+ type: string
+ description:
+ type: string
+ private_key:
+ type: string
+ type: object
+ additionalProperties: false
+ responses:
+ '201':
+ description: "The created private key's UUID."
+ content:
+ application/json:
+ schema:
+ properties:
+ uuid: { type: string }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ patch:
+ tags:
+ - 'Private Keys'
+ summary: Update
+ description: 'Update a private key.'
+ operationId: 371fd26b8949a070c26a264231fe233f
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ required:
+ - private_key
+ properties:
+ name:
+ type: string
+ description:
+ type: string
+ private_key:
+ type: string
+ type: object
+ additionalProperties: false
+ responses:
+ '201':
+ description: "The updated private key's UUID."
+ content:
+ application/json:
+ schema:
+ properties:
+ uuid: { type: string }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ '/security/keys/{uuid}':
+ get:
+ tags:
+ - 'Private Keys'
+ summary: Get
+ description: 'Get key by UUID.'
+ operationId: 2f743a85eb65d5ddb8cd5b362bb3d26a
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'Private Key Uuid'
+ required: true
+ schema:
+ type: integer
+ responses:
+ '200':
+ description: 'Get all private keys.'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/PrivateKey'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ description: 'Private Key not found.'
+ security:
+ -
+ bearerAuth: []
+ delete:
+ tags:
+ - 'Private Keys'
+ summary: Delete
+ description: 'Delete a private key.'
+ operationId: 8faa0bb399142f0084dfc3e003c42cf6
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'Private Key Uuid'
+ required: true
+ schema:
+ type: integer
+ responses:
+ '200':
+ description: 'Private Key deleted.'
+ content:
+ application/json:
+ schema:
+ properties:
+ message: { type: string, example: 'Private Key deleted.' }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ description: 'Private Key not found.'
+ security:
+ -
+ bearerAuth: []
+ /servers:
+ get:
+ tags:
+ - Servers
+ summary: List
+ description: 'List all servers.'
+ operationId: 787448df856cefd2d9a313566be30d34
+ responses:
+ '200':
+ description: 'Get all servers.'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Server'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ '/servers/{uuid}':
+ get:
+ tags:
+ - Servers
+ summary: Get
+ description: 'Get server by UUID.'
+ operationId: 5baf04bddb8302c7e07f5b4c41aad10c
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: "Server's Uuid"
+ required: true
+ schema:
+ type: integer
+ responses:
+ '200':
+ description: 'Get server by UUID'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Server'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ '/servers/{uuid}/resources':
+ get:
+ tags:
+ - Servers
+ summary: Resources
+ description: 'Get resources by server.'
+ operationId: cef26c059941b44fbd8de3a7a58c10a5
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: "Server's Uuid"
+ required: true
+ schema:
+ type: integer
+ responses:
+ '200':
+ description: 'Get resources by server'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ properties: { id: { type: integer }, uuid: { type: string }, name: { type: string }, type: { type: string }, created_at: { type: string }, updated_at: { type: string }, status: { type: string } }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ '/servers/{uuid}/domains':
+ get:
+ tags:
+ - Servers
+ summary: Domains
+ description: 'Get domains by server.'
+ operationId: 1ee227755be848d572f412272f53dd93
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: "Server's Uuid"
+ required: true
+ schema:
+ type: integer
+ responses:
+ '200':
+ description: 'Get domains by server'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ properties: { ip: { type: string }, domains: { type: array, items: { type: string } } }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ /services:
+ get:
+ tags:
+ - Services
+ summary: List
+ description: 'List all services.'
+ operationId: 5d014ac25d33391b8f4c2316060ba452
+ responses:
+ '200':
+ description: 'Get all services'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Service'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ post:
+ tags:
+ - Services
+ summary: Create
+ description: 'Create a one-click service'
+ operationId: 3d6cbfb54d919b53ba3984a113e837d7
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ required:
+ - server_uuid
+ - project_uuid
+ - environment_name
+ - type
+ properties:
+ type:
+ description: 'The one-click service type'
+ type: string
+ enum: [activepieces, appsmith, appwrite, authentik, babybuddy, budge, changedetection, chatwoot, classicpress-with-mariadb, classicpress-with-mysql, classicpress-without-database, cloudflared, code-server, dashboard, directus, directus-with-postgresql, docker-registry, docuseal, docuseal-with-postgres, dokuwiki, duplicati, emby, embystat, fider, filebrowser, firefly, formbricks, ghost, gitea, gitea-with-mariadb, gitea-with-mysql, gitea-with-postgresql, glance, glances, glitchtip, grafana, grafana-with-postgresql, grocy, heimdall, homepage, jellyfin, kuzzle, listmonk, logto, mediawiki, meilisearch, metabase, metube, minio, moodle, n8n, n8n-with-postgresql, next-image-transformation, nextcloud, nocodb, odoo, openblocks, pairdrop, penpot, phpmyadmin, pocketbase, posthog, reactive-resume, rocketchat, shlink, slash, snapdrop, statusnook, stirling-pdf, supabase, syncthing, tolgee, trigger, trigger-with-external-database, twenty, umami, unleash-with-postgresql, unleash-without-database, uptime-kuma, vaultwarden, vikunja, weblate, whoogle, wordpress-with-mariadb, wordpress-with-mysql, wordpress-without-database]
+ name:
+ type: string
+ maxLength: 255
+ description: 'Name of the service.'
+ description:
+ type: string
+ nullable: true
+ description: 'Description of the service.'
+ project_uuid:
+ type: string
+ description: 'Project UUID.'
+ environment_name:
+ type: string
+ description: 'Environment name.'
+ server_uuid:
+ type: string
+ description: 'Server UUID.'
+ destination_uuid:
+ type: string
+ description: 'Destination UUID. Required if server has multiple destinations.'
+ instant_deploy:
+ type: boolean
+ default: false
+ description: 'Start the service immediately after creation.'
+ type: object
+ responses:
+ '201':
+ description: 'Create a service.'
+ content:
+ application/json:
+ schema:
+ properties:
+ uuid: { type: string, description: 'Service UUID.' }
+ domains: { type: array, items: { type: string }, description: 'Service domains.' }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ '/services/{uuid}':
+ get:
+ tags:
+ - Services
+ summary: Get
+ description: 'Get service by UUID.'
+ operationId: 895d39ee2cb3994285de57256c2d428d
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'Service UUID'
+ required: true
+ schema:
+ type: string
+ responses:
+ '200':
+ description: 'Get a service by Uuid.'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Service'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ delete:
+ tags:
+ - Services
+ summary: Delete
+ description: 'Delete service by UUID.'
+ operationId: 6e1a61e4fddaa9d95bb9fc66dfaf0442
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'Service UUID'
+ required: true
+ schema:
+ type: string
+ responses:
+ '200':
+ description: 'Delete a service by Uuid'
+ content:
+ application/json:
+ schema:
+ properties:
+ message: { type: string, example: 'Service deletion request queued.' }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ '/services/{uuid}/start':
+ get:
+ tags:
+ - Services
+ summary: Start
+ description: 'Start service. `Post` request is also accepted.'
+ operationId: d2ddd9c028d123fbdec830dc4b25b4cb
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'UUID of the service.'
+ required: true
+ schema:
+ type: string
+ format: uuid
+ responses:
+ '200':
+ description: 'Start service.'
+ content:
+ application/json:
+ schema:
+ properties:
+ message: { type: string, example: 'Service starting request queued.' }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ '/services/{uuid}/stop':
+ get:
+ tags:
+ - Services
+ summary: Stop
+ description: 'Stop service. `Post` request is also accepted.'
+ operationId: 87399d34758ce16830740c68626614db
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'UUID of the service.'
+ required: true
+ schema:
+ type: string
+ format: uuid
+ responses:
+ '200':
+ description: 'Stop service.'
+ content:
+ application/json:
+ schema:
+ properties:
+ message: { type: string, example: 'Service stopping request queued.' }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ '/services/{uuid}/restart':
+ get:
+ tags:
+ - Services
+ summary: Restart
+ description: 'Restart service. `Post` request is also accepted.'
+ operationId: 836645faa615b75052759dae78639469
+ parameters:
+ -
+ name: uuid
+ in: path
+ description: 'UUID of the service.'
+ required: true
+ schema:
+ type: string
+ format: uuid
+ responses:
+ '200':
+ description: 'Restart service.'
+ content:
+ application/json:
+ schema:
+ properties:
+ message: { type: string, example: 'Service restaring request queued.' }
+ type: object
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ /teams:
+ get:
+ tags:
+ - Teams
+ summary: List
+ description: 'Get all teams.'
+ operationId: f9c530b5b25df9601cb87d6a58646f0a
+ responses:
+ '200':
+ description: 'List of teams.'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Team'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ '/teams/{id}':
+ get:
+ tags:
+ - Teams
+ summary: Get
+ description: 'Get team by TeamId.'
+ operationId: ac57ff546c002032cef44602c46a4e76
+ parameters:
+ -
+ name: id
+ in: path
+ description: 'Team ID'
+ required: true
+ schema:
+ type: integer
+ responses:
+ '200':
+ description: 'List of teams.'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Team'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ '/teams/{id}/members':
+ get:
+ tags:
+ - Teams
+ summary: Members
+ description: 'Get members by TeamId.'
+ operationId: 7858f5a45d9ea55184c182852a7f0f6c
+ parameters:
+ -
+ name: id
+ in: path
+ description: 'Team ID'
+ required: true
+ schema:
+ type: integer
+ responses:
+ '200':
+ description: 'List of members.'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/User'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ '404':
+ $ref: '#/components/responses/404'
+ security:
+ -
+ bearerAuth: []
+ /teams/current:
+ get:
+ tags:
+ - Teams
+ summary: 'Authenticated Team'
+ description: 'Get currently authenticated team.'
+ operationId: 6a4ec9fed1aad7b0b38356c47d7ac509
+ responses:
+ '200':
+ description: 'Current Team.'
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Team'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+ /teams/current/members:
+ get:
+ tags:
+ - Teams
+ summary: 'Authenticated Team Members'
+ description: 'Get currently authenticated team members.'
+ operationId: 97e636a5796dbe71afb0bbcf1eec6e41
+ responses:
+ '200':
+ description: 'Currently authenticated team members.'
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/User'
+ '401':
+ $ref: '#/components/responses/401'
+ '400':
+ $ref: '#/components/responses/400'
+ security:
+ -
+ bearerAuth: []
+components:
+ schemas:
+ Application:
+ description: 'Application model'
+ properties:
+ id:
+ type: integer
+ description: 'The application identifier in the database.'
+ description:
+ type: string
+ nullable: true
+ description: 'The application description.'
+ repository_project_id:
+ type: integer
+ nullable: true
+ description: 'The repository project identifier.'
+ uuid:
+ type: string
+ description: 'The application UUID.'
+ name:
+ type: string
+ description: 'The application name.'
+ fqdn:
+ type: string
+ nullable: true
+ description: 'The application domains.'
+ config_hash:
+ type: string
+ description: 'Configuration hash.'
+ git_repository:
+ type: string
+ description: 'Git repository URL.'
+ git_branch:
+ type: string
+ description: 'Git branch.'
+ git_commit_sha:
+ type: string
+ description: 'Git commit SHA.'
+ git_full_url:
+ type: string
+ nullable: true
+ description: 'Git full URL.'
+ docker_registry_image_name:
+ type: string
+ nullable: true
+ description: 'Docker registry image name.'
+ docker_registry_image_tag:
+ type: string
+ nullable: true
+ description: 'Docker registry image tag.'
+ build_pack:
+ type: string
+ description: 'Build pack.'
+ enum:
+ - nixpacks
+ - static
+ - dockerfile
+ - dockercompose
+ static_image:
+ type: string
+ description: 'Static image used when static site is deployed.'
+ install_command:
+ type: string
+ description: 'Install command.'
+ build_command:
+ type: string
+ description: 'Build command.'
+ start_command:
+ type: string
+ description: 'Start command.'
+ ports_exposes:
+ type: string
+ description: 'Ports exposes.'
+ ports_mappings:
+ type: string
+ nullable: true
+ description: 'Ports mappings.'
+ base_directory:
+ type: string
+ description: 'Base directory for all commands.'
+ publish_directory:
+ type: string
+ description: 'Publish directory.'
+ health_check_enabled:
+ type: boolean
+ description: 'Health check enabled.'
+ health_check_path:
+ type: string
+ description: 'Health check path.'
+ health_check_port:
+ type: string
+ nullable: true
+ description: 'Health check port.'
+ health_check_host:
+ type: string
+ nullable: true
+ description: 'Health check host.'
+ health_check_method:
+ type: string
+ description: 'Health check method.'
+ health_check_return_code:
+ type: integer
+ description: 'Health check return code.'
+ health_check_scheme:
+ type: string
+ description: 'Health check scheme.'
+ health_check_response_text:
+ type: string
+ nullable: true
+ description: 'Health check response text.'
+ health_check_interval:
+ type: integer
+ description: 'Health check interval in seconds.'
+ health_check_timeout:
+ type: integer
+ description: 'Health check timeout in seconds.'
+ health_check_retries:
+ type: integer
+ description: 'Health check retries count.'
+ health_check_start_period:
+ type: integer
+ description: 'Health check start period in seconds.'
+ limits_memory:
+ type: string
+ description: 'Memory limit.'
+ limits_memory_swap:
+ type: string
+ description: 'Memory swap limit.'
+ limits_memory_swappiness:
+ type: integer
+ description: 'Memory swappiness.'
+ limits_memory_reservation:
+ type: string
+ description: 'Memory reservation.'
+ limits_cpus:
+ type: string
+ description: 'CPU limit.'
+ limits_cpuset:
+ type: string
+ nullable: true
+ description: 'CPU set.'
+ limits_cpu_shares:
+ type: integer
+ description: 'CPU shares.'
+ status:
+ type: string
+ description: 'Application status.'
+ preview_url_template:
+ type: string
+ description: 'Preview URL template.'
+ destination_type:
+ type: string
+ description: 'Destination type.'
+ destination_id:
+ type: integer
+ description: 'Destination identifier.'
+ source_id:
+ type: integer
+ nullable: true
+ description: 'Source identifier.'
+ private_key_id:
+ type: integer
+ nullable: true
+ description: 'Private key identifier.'
+ environment_id:
+ type: integer
+ description: 'Environment identifier.'
+ dockerfile:
+ type: string
+ nullable: true
+ description: 'Dockerfile content. Used for dockerfile build pack.'
+ dockerfile_location:
+ type: string
+ description: 'Dockerfile location.'
+ custom_labels:
+ type: string
+ nullable: true
+ description: 'Custom labels.'
+ dockerfile_target_build:
+ type: string
+ nullable: true
+ description: 'Dockerfile target build.'
+ manual_webhook_secret_github:
+ type: string
+ nullable: true
+ description: 'Manual webhook secret for GitHub.'
+ manual_webhook_secret_gitlab:
+ type: string
+ nullable: true
+ description: 'Manual webhook secret for GitLab.'
+ manual_webhook_secret_bitbucket:
+ type: string
+ nullable: true
+ description: 'Manual webhook secret for Bitbucket.'
+ manual_webhook_secret_gitea:
+ type: string
+ nullable: true
+ description: 'Manual webhook secret for Gitea.'
+ docker_compose_location:
+ type: string
+ description: 'Docker compose location.'
+ docker_compose:
+ type: string
+ nullable: true
+ description: 'Docker compose content. Used for docker compose build pack.'
+ docker_compose_raw:
+ type: string
+ nullable: true
+ description: 'Docker compose raw content.'
+ docker_compose_domains:
+ type: string
+ nullable: true
+ description: 'Docker compose domains.'
+ docker_compose_custom_start_command:
+ type: string
+ nullable: true
+ description: 'Docker compose custom start command.'
+ docker_compose_custom_build_command:
+ type: string
+ nullable: true
+ description: 'Docker compose custom build command.'
+ swarm_replicas:
+ type: integer
+ nullable: true
+ description: 'Swarm replicas. Only used for swarm deployments.'
+ swarm_placement_constraints:
+ type: string
+ nullable: true
+ description: 'Swarm placement constraints. Only used for swarm deployments.'
+ custom_docker_run_options:
+ type: string
+ nullable: true
+ description: 'Custom docker run options.'
+ post_deployment_command:
+ type: string
+ nullable: true
+ description: 'Post deployment command.'
+ post_deployment_command_container:
+ type: string
+ nullable: true
+ description: 'Post deployment command container.'
+ pre_deployment_command:
+ type: string
+ nullable: true
+ description: 'Pre deployment command.'
+ pre_deployment_command_container:
+ type: string
+ nullable: true
+ description: 'Pre deployment command container.'
+ watch_paths:
+ type: string
+ nullable: true
+ description: 'Watch paths.'
+ custom_healthcheck_found:
+ type: boolean
+ description: 'Custom healthcheck found.'
+ redirect:
+ type: string
+ nullable: true
+ description: 'How to set redirect with Traefik / Caddy. www<->non-www.'
+ enum:
+ - www
+ - non-www
+ - both
+ created_at:
+ type: string
+ format: date-time
+ description: 'The date and time when the application was created.'
+ updated_at:
+ type: string
+ format: date-time
+ description: 'The date and time when the application was last updated.'
+ deleted_at:
+ type: string
+ format: date-time
+ nullable: true
+ description: 'The date and time when the application was deleted.'
+ type: object
+ ApplicationDeploymentQueue:
+ description: 'Project model'
+ properties:
+ id:
+ type: integer
+ application_id:
+ type: string
+ deployment_uuid:
+ type: string
+ pull_request_id:
+ type: integer
+ force_rebuild:
+ type: boolean
+ commit:
+ type: string
+ status:
+ type: string
+ is_webhook:
+ type: boolean
+ is_api:
+ type: boolean
+ created_at:
+ type: string
+ updated_at:
+ type: string
+ logs:
+ type: string
+ current_process_id:
+ type: string
+ restart_only:
+ type: boolean
+ git_type:
+ type: string
+ server_id:
+ type: integer
+ application_name:
+ type: string
+ server_name:
+ type: string
+ deployment_url:
+ type: string
+ destination_id:
+ type: string
+ only_this_server:
+ type: boolean
+ rollback:
+ type: boolean
+ commit_message:
+ type: string
+ type: object
+ Environment:
+ description: 'Environment model'
+ properties:
+ id:
+ type: integer
+ name:
+ type: string
+ project_id:
+ type: integer
+ created_at:
+ type: string
+ updated_at:
+ type: string
+ description:
+ type: string
+ type: object
+ EnvironmentVariable:
+ description: 'Environment Variable model'
+ properties:
+ id:
+ type: integer
+ uuid:
+ type: string
+ application_id:
+ type: integer
+ service_id:
+ type: integer
+ database_id:
+ type: integer
+ is_build_time:
+ type: boolean
+ is_literal:
+ type: boolean
+ is_multiline:
+ type: boolean
+ is_preview:
+ type: boolean
+ is_shared:
+ type: boolean
+ is_shown_once:
+ type: boolean
+ key:
+ type: string
+ value:
+ type: string
+ real_value:
+ type: string
+ version:
+ type: string
+ created_at:
+ type: string
+ updated_at:
+ type: string
+ type: object
+ PrivateKey:
+ description: 'Private Key model'
+ properties:
+ id:
+ type: integer
+ uuid:
+ type: string
+ name:
+ type: string
+ description:
+ type: string
+ private_key:
+ type: string
+ format: private-key
+ is_git_related:
+ type: boolean
+ team_id:
+ type: integer
+ created_at:
+ type: string
+ updated_at:
+ type: string
+ type: object
+ Project:
+ description: 'Project model'
+ properties:
+ id:
+ type: integer
+ uuid:
+ type: string
+ name:
+ type: string
+ environments:
+ description: 'The environments of the project.'
+ type: array
+ items:
+ $ref: '#/components/schemas/Environment'
+ type: object
+ Server:
+ description: 'Application model'
+ properties:
+ id:
+ type: integer
+ repository_project_id:
+ type: integer
+ nullable: true
+ uuid:
+ type: string
+ name:
+ type: string
+ fqdn:
+ type: string
+ config_hash:
+ type: string
+ git_repository:
+ type: string
+ git_branch:
+ type: string
+ git_commit_sha:
+ type: string
+ git_full_url:
+ type: string
+ nullable: true
+ docker_registry_image_name:
+ type: string
+ nullable: true
+ docker_registry_image_tag:
+ type: string
+ nullable: true
+ build_pack:
+ type: string
+ static_image:
+ type: string
+ install_command:
+ type: string
+ build_command:
+ type: string
+ start_command:
+ type: string
+ ports_exposes:
+ type: string
+ ports_mappings:
+ type: string
+ nullable: true
+ base_directory:
+ type: string
+ publish_directory:
+ type: string
+ health_check_path:
+ type: string
+ health_check_port:
+ type: string
+ nullable: true
+ health_check_host:
+ type: string
+ health_check_method:
+ type: string
+ health_check_return_code:
+ type: integer
+ health_check_scheme:
+ type: string
+ health_check_response_text:
+ type: string
+ nullable: true
+ health_check_interval:
+ type: integer
+ health_check_timeout:
+ type: integer
+ health_check_retries:
+ type: integer
+ health_check_start_period:
+ type: integer
+ limits_memory:
+ type: string
+ limits_memory_swap:
+ type: string
+ limits_memory_swappiness:
+ type: integer
+ limits_memory_reservation:
+ type: string
+ limits_cpus:
+ type: string
+ limits_cpuset:
+ type: string
+ nullable: true
+ limits_cpu_shares:
+ type: integer
+ status:
+ type: string
+ preview_url_template:
+ type: string
+ destination_type:
+ type: string
+ destination_id:
+ type: integer
+ source_type:
+ type: string
+ source_id:
+ type: integer
+ private_key_id:
+ type: integer
+ nullable: true
+ environment_id:
+ type: integer
+ created_at:
+ type: string
+ format: date-time
+ updated_at:
+ type: string
+ format: date-time
+ description:
+ type: string
+ nullable: true
+ dockerfile:
+ type: string
+ nullable: true
+ health_check_enabled:
+ type: boolean
+ dockerfile_location:
+ type: string
+ custom_labels:
+ type: string
+ dockerfile_target_build:
+ type: string
+ nullable: true
+ manual_webhook_secret_github:
+ type: string
+ nullable: true
+ manual_webhook_secret_gitlab:
+ type: string
+ nullable: true
+ docker_compose_location:
+ type: string
+ docker_compose:
+ type: string
+ nullable: true
+ docker_compose_raw:
+ type: string
+ nullable: true
+ docker_compose_domains:
+ type: string
+ nullable: true
+ deleted_at:
+ type: string
+ format: date-time
+ nullable: true
+ docker_compose_custom_start_command:
+ type: string
+ nullable: true
+ docker_compose_custom_build_command:
+ type: string
+ nullable: true
+ swarm_replicas:
+ type: integer
+ swarm_placement_constraints:
+ type: string
+ nullable: true
+ manual_webhook_secret_bitbucket:
+ type: string
+ nullable: true
+ custom_docker_run_options:
+ type: string
+ nullable: true
+ post_deployment_command:
+ type: string
+ nullable: true
+ post_deployment_command_container:
+ type: string
+ nullable: true
+ pre_deployment_command:
+ type: string
+ nullable: true
+ pre_deployment_command_container:
+ type: string
+ nullable: true
+ watch_paths:
+ type: string
+ nullable: true
+ custom_healthcheck_found:
+ type: boolean
+ manual_webhook_secret_gitea:
+ type: string
+ nullable: true
+ redirect:
+ type: string
+ type: object
+ ServerSetting:
+ description: 'Server Settings model'
+ properties:
+ id:
+ type: integer
+ cleanup_after_percentage:
+ type: integer
+ concurrent_builds:
+ type: integer
+ dynamic_timeout:
+ type: integer
+ force_disabled:
+ type: boolean
+ is_build_server:
+ type: boolean
+ is_cloudflare_tunnel:
+ type: boolean
+ is_jump_server:
+ type: boolean
+ is_logdrain_axiom_enabled:
+ type: boolean
+ is_logdrain_custom_enabled:
+ type: boolean
+ is_logdrain_highlight_enabled:
+ type: boolean
+ is_logdrain_newrelic_enabled:
+ type: boolean
+ is_metrics_enabled:
+ type: boolean
+ is_reachable:
+ type: boolean
+ is_server_api_enabled:
+ type: boolean
+ is_swarm_manager:
+ type: boolean
+ is_swarm_worker:
+ type: boolean
+ is_usable:
+ type: boolean
+ logdrain_axiom_api_key:
+ type: string
+ logdrain_axiom_dataset_name:
+ type: string
+ logdrain_custom_config:
+ type: string
+ logdrain_custom_config_parser:
+ type: string
+ logdrain_highlight_project_id:
+ type: string
+ logdrain_newrelic_base_uri:
+ type: string
+ logdrain_newrelic_license_key:
+ type: string
+ metrics_history_days:
+ type: integer
+ metrics_refresh_rate_seconds:
+ type: integer
+ metrics_token:
+ type: string
+ server_id:
+ type: integer
+ wildcard_domain:
+ type: string
+ created_at:
+ type: string
+ updated_at:
+ type: string
+ type: object
+ Service:
+ description: 'Service model'
+ properties:
+ id:
+ type: integer
+ description: 'The unique identifier of the service. Only used for database identification.'
+ uuid:
+ type: string
+ description: 'The unique identifier of the service.'
+ name:
+ type: string
+ description: 'The name of the service.'
+ environment_id:
+ type: integer
+ description: 'The unique identifier of the environment where the service is attached to.'
+ server_id:
+ type: integer
+ description: 'The unique identifier of the server where the service is running.'
+ description:
+ type: string
+ description: 'The description of the service.'
+ docker_compose_raw:
+ type: string
+ description: 'The raw docker-compose.yml file of the service.'
+ docker_compose:
+ type: string
+ description: 'The docker-compose.yml file that is parsed and modified by Coolify.'
+ destination_id:
+ type: integer
+ description: 'The unique identifier of the destination where the service is running.'
+ connect_to_docker_network:
+ type: boolean
+ description: 'The flag to connect the service to the predefined Docker network.'
+ is_container_label_escape_enabled:
+ type: boolean
+ description: 'The flag to enable the container label escape.'
+ config_hash:
+ type: string
+ description: 'The hash of the service configuration.'
+ service_type:
+ type: string
+ description: 'The type of the service.'
+ created_at:
+ type: string
+ description: 'The date and time when the service was created.'
+ updated_at:
+ type: string
+ description: 'The date and time when the service was last updated.'
+ deleted_at:
+ type: string
+ description: 'The date and time when the service was deleted.'
+ type: object
+ Team:
+ description: 'Team model'
+ properties:
+ id:
+ type: integer
+ description: 'The unique identifier of the team.'
+ name:
+ type: string
+ description: 'The name of the team.'
+ description:
+ type: string
+ description: 'The description of the team.'
+ personal_team:
+ type: boolean
+ description: 'Whether the team is personal or not.'
+ created_at:
+ type: string
+ description: 'The date and time the team was created.'
+ updated_at:
+ type: string
+ description: 'The date and time the team was last updated.'
+ smtp_enabled:
+ type: boolean
+ description: 'Whether SMTP is enabled or not.'
+ smtp_from_address:
+ type: string
+ description: 'The email address to send emails from.'
+ smtp_from_name:
+ type: string
+ description: 'The name to send emails from.'
+ smtp_recipients:
+ type: string
+ description: 'The email addresses to send emails to.'
+ smtp_host:
+ type: string
+ description: 'The SMTP host.'
+ smtp_port:
+ type: string
+ description: 'The SMTP port.'
+ smtp_encryption:
+ type: string
+ description: 'The SMTP encryption.'
+ smtp_username:
+ type: string
+ description: 'The SMTP username.'
+ smtp_password:
+ type: string
+ description: 'The SMTP password.'
+ smtp_timeout:
+ type: string
+ description: 'The SMTP timeout.'
+ smtp_notifications_test:
+ type: boolean
+ description: 'Whether to send test notifications via SMTP.'
+ smtp_notifications_deployments:
+ type: boolean
+ description: 'Whether to send deployment notifications via SMTP.'
+ smtp_notifications_status_changes:
+ type: boolean
+ description: 'Whether to send status change notifications via SMTP.'
+ smtp_notifications_scheduled_tasks:
+ type: boolean
+ description: 'Whether to send scheduled task notifications via SMTP.'
+ smtp_notifications_database_backups:
+ type: boolean
+ description: 'Whether to send database backup notifications via SMTP.'
+ discord_enabled:
+ type: boolean
+ description: 'Whether Discord is enabled or not.'
+ discord_webhook_url:
+ type: string
+ description: 'The Discord webhook URL.'
+ discord_notifications_test:
+ type: boolean
+ description: 'Whether to send test notifications via Discord.'
+ discord_notifications_deployments:
+ type: boolean
+ description: 'Whether to send deployment notifications via Discord.'
+ discord_notifications_status_changes:
+ type: boolean
+ description: 'Whether to send status change notifications via Discord.'
+ discord_notifications_database_backups:
+ type: boolean
+ description: 'Whether to send database backup notifications via Discord.'
+ discord_notifications_scheduled_tasks:
+ type: boolean
+ description: 'Whether to send scheduled task notifications via Discord.'
+ show_boarding:
+ type: boolean
+ description: 'Whether to show the boarding screen or not.'
+ resend_enabled:
+ type: boolean
+ description: 'Whether to enable resending or not.'
+ resend_api_key:
+ type: string
+ description: 'The resending API key.'
+ use_instance_email_settings:
+ type: boolean
+ description: 'Whether to use instance email settings or not.'
+ telegram_enabled:
+ type: boolean
+ description: 'Whether Telegram is enabled or not.'
+ telegram_token:
+ type: string
+ description: 'The Telegram token.'
+ telegram_chat_id:
+ type: string
+ description: 'The Telegram chat ID.'
+ telegram_notifications_test:
+ type: boolean
+ description: 'Whether to send test notifications via Telegram.'
+ telegram_notifications_deployments:
+ type: boolean
+ description: 'Whether to send deployment notifications via Telegram.'
+ telegram_notifications_status_changes:
+ type: boolean
+ description: 'Whether to send status change notifications via Telegram.'
+ telegram_notifications_database_backups:
+ type: boolean
+ description: 'Whether to send database backup notifications via Telegram.'
+ telegram_notifications_test_message_thread_id:
+ type: string
+ description: 'The Telegram test message thread ID.'
+ telegram_notifications_deployments_message_thread_id:
+ type: string
+ description: 'The Telegram deployment message thread ID.'
+ telegram_notifications_status_changes_message_thread_id:
+ type: string
+ description: 'The Telegram status change message thread ID.'
+ telegram_notifications_database_backups_message_thread_id:
+ type: string
+ description: 'The Telegram database backup message thread ID.'
+ custom_server_limit:
+ type: string
+ description: 'The custom server limit.'
+ telegram_notifications_scheduled_tasks:
+ type: boolean
+ description: 'Whether to send scheduled task notifications via Telegram.'
+ telegram_notifications_scheduled_tasks_thread_id:
+ type: string
+ description: 'The Telegram scheduled task message thread ID.'
+ members:
+ description: 'The members of the team.'
+ type: array
+ items:
+ $ref: '#/components/schemas/User'
+ type: object
+ User:
+ description: 'User model'
+ properties:
+ id:
+ type: integer
+ description: 'The user identifier in the database.'
+ name:
+ type: string
+ description: 'The user name.'
+ email:
+ type: string
+ description: 'The user email.'
+ email_verified_at:
+ type: string
+ description: 'The date when the user email was verified.'
+ created_at:
+ type: string
+ description: 'The date when the user was created.'
+ updated_at:
+ type: string
+ description: 'The date when the user was updated.'
+ two_factor_confirmed_at:
+ type: string
+ description: 'The date when the user two factor was confirmed.'
+ force_password_reset:
+ type: boolean
+ description: 'The flag to force the user to reset the password.'
+ marketing_emails:
+ type: boolean
+ description: 'The flag to receive marketing emails.'
+ type: object
+ responses:
+ '400':
+ description: 'Invalid token.'
+ content:
+ application/json:
+ schema:
+ properties:
+ message:
+ type: string
+ example: 'Invalid token.'
+ type: object
+ '401':
+ description: Unauthenticated.
+ content:
+ application/json:
+ schema:
+ properties:
+ message:
+ type: string
+ example: Unauthenticated.
+ type: object
+ '404':
+ description: 'Resource not found.'
+ content:
+ application/json:
+ schema:
+ properties:
+ message:
+ type: string
+ example: 'Resource not found.'
+ type: object
+ securitySchemes:
+ bearerAuth:
+ type: http
+ description: 'Go to `Keys & Tokens` / `API tokens` and create a new token. Use the token as the bearer token.'
+ scheme: bearer
+tags:
+ -
+ name: Applications
+ description: Applications
+ -
+ name: Databases
+ description: Databases
+ -
+ name: Deployments
+ description: Deployments
+ -
+ name: Projects
+ description: Projects
+ -
+ name: Resources
+ description: Resources
+ -
+ name: 'Private Keys'
+ description: 'Private Keys'
+ -
+ name: Servers
+ description: Servers
+ -
+ name: Services
+ description: Services
+ -
+ name: Teams
+ description: Teams
diff --git a/resources/views/livewire/notifications/email.blade.php b/resources/views/livewire/notifications/email.blade.php
index f911251d5..594cf427b 100644
--- a/resources/views/livewire/notifications/email.blade.php
+++ b/resources/views/livewire/notifications/email.blade.php
@@ -3,7 +3,7 @@
Notifications | Coolify