2024-06-21 14:46:13 +00:00
< ? php
namespace App\Http\Controllers\Api ;
2024-07-02 14:12:04 +00:00
use App\Actions\Application\LoadComposeFile ;
2024-06-21 14:46:13 +00:00
use App\Actions\Application\StopApplication ;
2024-07-02 14:12:04 +00:00
use App\Actions\Service\StartService ;
2024-06-28 13:05:37 +00:00
use App\Enums\BuildPackTypes ;
2024-06-21 14:46:13 +00:00
use App\Http\Controllers\Controller ;
2024-06-26 11:00:36 +00:00
use App\Jobs\DeleteResourceJob ;
2024-06-21 14:46:13 +00:00
use App\Models\Application ;
2024-06-26 11:00:36 +00:00
use App\Models\EnvironmentVariable ;
2024-06-30 09:30:31 +00:00
use App\Models\GithubApp ;
use App\Models\PrivateKey ;
2024-06-21 14:46:13 +00:00
use App\Models\Project ;
2024-06-28 13:05:37 +00:00
use App\Models\Server ;
2024-07-01 14:26:50 +00:00
use App\Models\Service ;
2024-06-21 14:46:13 +00:00
use Illuminate\Http\Request ;
2024-06-26 11:00:36 +00:00
use Illuminate\Validation\Rule ;
2024-07-09 08:45:10 +00:00
use OpenApi\Attributes as OA ;
2024-07-01 14:26:50 +00:00
use Symfony\Component\Yaml\Yaml ;
2024-06-21 14:46:13 +00:00
use Visus\Cuid2\Cuid2 ;
2024-07-01 14:26:50 +00:00
class ApplicationsController extends Controller
2024-06-21 14:46:13 +00:00
{
2024-07-02 10:15:58 +00:00
private function removeSensitiveData ( $application )
{
$token = auth () -> user () -> currentAccessToken ();
2024-07-04 11:45:06 +00:00
$application -> makeHidden ([
'id' ,
]);
2024-07-02 10:15:58 +00:00
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' ,
2024-07-04 11:45:06 +00:00
'value' ,
'real_value' ,
2024-07-02 10:15:58 +00:00
]);
return serializeApiResponse ( $application );
}
2024-07-09 08:45:10 +00:00
#[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' ,
),
]
)]
2024-06-21 14:46:13 +00:00
public function applications ( Request $request )
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-21 14:46:13 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
2024-06-21 14:46:13 +00:00
}
$projects = Project :: where ( 'team_id' , $teamId ) -> get ();
$applications = collect ();
$applications -> push ( $projects -> pluck ( 'applications' ) -> flatten ());
$applications = $applications -> flatten ();
2024-07-01 14:26:50 +00:00
$applications = $applications -> map ( function ( $application ) {
2024-07-02 10:15:58 +00:00
return $this -> removeSensitiveData ( $application );
2024-07-01 14:26:50 +00:00
});
2024-06-21 14:46:13 +00:00
2024-07-03 11:13:38 +00:00
return response () -> json ( $applications );
2024-06-21 14:46:13 +00:00
}
2024-07-09 11:19:21 +00:00
#[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 : [
2024-07-09 12:12:36 +00:00
'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.' ],
2024-07-09 11:19:21 +00:00
],
)),
]),
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' ,
),
]
)]
2024-07-04 11:45:06 +00:00
public function create_public_application ( Request $request )
{
2024-07-09 11:19:21 +00:00
return $this -> create_application ( $request , 'public' );
2024-07-04 11:45:06 +00:00
}
2024-07-09 11:19:21 +00:00
#[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 : [
2024-07-09 12:12:36 +00:00
'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.' ],
2024-07-09 11:19:21 +00:00
],
)),
]),
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' ,
),
]
)]
2024-07-04 11:45:06 +00:00
public function create_private_gh_app_application ( Request $request )
{
2024-07-09 11:19:21 +00:00
return $this -> create_application ( $request , 'private-gh-app' );
2024-07-04 11:45:06 +00:00
}
2024-07-09 08:45:10 +00:00
#[OA\Post(
2024-07-09 11:19:21 +00:00
summary : 'Create (Private - Deploy Key)' ,
description : 'Create new application based on a private repository through a Deploy Key.' ,
path : '/applications/private-deploy-key' ,
2024-07-09 08:45:10 +00:00
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' ,
2024-07-09 12:12:36 +00:00
required : [ 'project_uuid' , 'server_uuid' , 'environment_name' , 'private_key_uuid' , 'git_repository' , 'git_branch' , 'build_pack' , 'ports_exposes' ],
2024-07-09 08:45:10 +00:00
properties : [
2024-07-09 12:12:36 +00:00
'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.' ],
2024-07-09 08:45:10 +00:00
],
)),
]),
responses : [
new OA\Response (
response : 200 ,
2024-07-09 11:19:21 +00:00
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 : [
2024-07-09 12:12:36 +00:00
'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.' ],
2024-07-09 11:19:21 +00:00
],
)),
]),
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 : [
2024-07-09 12:12:36 +00:00
'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.' ],
2024-07-09 11:19:21 +00:00
],
)),
]),
responses : [
new OA\Response (
response : 200 ,
description : 'Application created successfully.' ,
),
2024-07-09 08:45:10 +00:00
new OA\Response (
response : 401 ,
ref : '#/components/responses/401' ,
),
new OA\Response (
response : 400 ,
ref : '#/components/responses/400' ,
),
]
)]
2024-07-09 11:19:21 +00:00
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 : [
2024-07-09 12:12:36 +00:00
'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.' ],
2024-07-09 11:19:21 +00:00
],
)),
]),
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' );
}
2024-07-04 11:45:06 +00:00
private function create_application ( Request $request , $type )
2024-06-28 13:05:37 +00:00
{
2024-07-09 08:45:10 +00:00
$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' ];
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-28 13:05:37 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
2024-06-28 13:05:37 +00:00
}
2024-07-01 14:26:50 +00:00
$return = validateIncomingRequest ( $request );
if ( $return instanceof \Illuminate\Http\JsonResponse ) {
return $return ;
2024-06-28 13:05:37 +00:00
}
$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 );
}
2024-06-30 09:30:31 +00:00
2024-06-28 13:05:37 +00:00
$serverUuid = $request -> server_uuid ;
$fqdn = $request -> domains ;
$instantDeploy = $request -> instant_deploy ;
2024-06-30 09:30:31 +00:00
$githubAppUuid = $request -> github_app_uuid ;
2024-06-28 13:05:37 +00:00
$project = Project :: whereTeamId ( $teamId ) -> whereUuid ( $request -> project_uuid ) -> first ();
if ( ! $project ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Project not found.' ], 404 );
2024-06-28 13:05:37 +00:00
}
$environment = $project -> environments () -> where ( 'name' , $request -> environment_name ) -> first ();
if ( ! $environment ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Environment not found.' ], 404 );
2024-06-28 13:05:37 +00:00
}
$server = Server :: whereTeamId ( $teamId ) -> whereUuid ( $serverUuid ) -> first ();
if ( ! $server ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Server not found.' ], 404 );
2024-06-28 13:05:37 +00:00
}
$destinations = $server -> destinations ();
if ( $destinations -> count () == 0 ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Server has no destinations.' ], 400 );
2024-06-28 13:05:37 +00:00
}
if ( $destinations -> count () > 1 && ! $request -> has ( 'destination_uuid' )) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Server has multiple destinations and you do not set destination_uuid.' ], 400 );
2024-06-28 13:05:37 +00:00
}
$destination = $destinations -> first ();
if ( $type === 'public' ) {
2024-07-01 09:39:10 +00:00
if ( ! $request -> has ( 'name' )) {
$request -> offsetSet ( 'name' , generate_application_name ( $request -> git_repository , $request -> git_branch ));
}
2024-07-11 08:17:20 +00:00
if ( $request -> build_pack === 'dockercompose' ) {
$request -> offsetSet ( 'ports_exposes' , '80' );
}
2024-06-28 13:05:37 +00:00
$validator = customApiValidator ( $request -> all (), [
sharedDataApplications (),
'git_repository' => 'string|required' ,
'git_branch' => 'string|required' ,
2024-07-03 15:10:00 +00:00
'build_pack' => [ 'required' , Rule :: enum ( BuildPackTypes :: class )],
2024-06-28 13:05:37 +00:00
'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required' ,
2024-07-02 14:12:04 +00:00
'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' ,
2024-06-28 13:05:37 +00:00
]);
if ( $validator -> fails ()) {
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => $validator -> errors (),
], 422 );
}
2024-06-30 09:30:31 +00:00
$return = $this -> validateDataApplications ( $request , $server );
2024-06-28 13:05:37 +00:00
if ( $return instanceof \Illuminate\Http\JsonResponse ) {
return $return ;
}
$application = new Application ();
2024-07-01 14:26:50 +00:00
removeUnnecessaryFieldsFromRequest ( $request );
2024-06-28 13:05:37 +00:00
$application -> fill ( $request -> all ());
2024-07-02 14:12:04 +00:00
$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 ;
}
2024-06-28 13:05:37 +00:00
$application -> fqdn = $fqdn ;
$application -> destination_id = $destination -> id ;
$application -> destination_type = $destination -> getMorphClass ();
$application -> environment_id = $environment -> id ;
$application -> save ();
2024-07-03 15:10:00 +00:00
$application -> refresh ();
2024-07-17 12:52:40 +00:00
if ( ! $application -> settings -> is_container_label_readonly_enabled ) {
$application -> custom_labels = str ( implode ( '|coolify|' , generateLabelsApplication ( $application ))) -> replace ( '|coolify|' , " \n " );
$application -> save ();
}
2024-07-03 15:10:00 +00:00
$application -> isConfigurationChanged ( true );
2024-06-28 13:05:37 +00:00
if ( $instantDeploy ) {
$deployment_uuid = new Cuid2 ( 7 );
queue_application_deployment (
application : $application ,
deployment_uuid : $deployment_uuid ,
no_questions_asked : true ,
is_api : true ,
);
2024-07-02 14:12:04 +00:00
} else {
if ( $application -> build_pack === 'dockercompose' ) {
LoadComposeFile :: dispatch ( $application );
}
2024-06-28 13:05:37 +00:00
}
2024-07-03 15:10:00 +00:00
return response () -> json ( serializeApiResponse ([
'uuid' => data_get ( $application , 'uuid' ),
'domains' => data_get ( $application , 'domains' ),
]));
2024-06-30 09:30:31 +00:00
} elseif ( $type === 'private-gh-app' ) {
2024-07-01 09:39:10 +00:00
if ( ! $request -> has ( 'name' )) {
$request -> offsetSet ( 'name' , generate_application_name ( $request -> git_repository , $request -> git_branch ));
}
2024-07-11 08:17:20 +00:00
if ( $request -> build_pack === 'dockercompose' ) {
$request -> offsetSet ( 'ports_exposes' , '80' );
}
2024-06-30 09:30:31 +00:00
$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' ,
2024-07-02 14:12:04 +00:00
'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' ,
2024-06-30 09:30:31 +00:00
]);
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 ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Github App not found.' ], 404 );
2024-06-30 09:30:31 +00:00
}
$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 ();
2024-07-01 14:26:50 +00:00
removeUnnecessaryFieldsFromRequest ( $request );
2024-06-30 09:30:31 +00:00
$application -> fill ( $request -> all ());
2024-07-02 14:12:04 +00:00
$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 ;
}
2024-06-30 09:30:31 +00:00
$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 ();
2024-07-03 15:10:00 +00:00
$application -> refresh ();
2024-07-17 12:52:40 +00:00
if ( ! $application -> settings -> is_container_label_readonly_enabled ) {
$application -> custom_labels = str ( implode ( '|coolify|' , generateLabelsApplication ( $application ))) -> replace ( '|coolify|' , " \n " );
$application -> save ();
}
2024-07-03 15:10:00 +00:00
$application -> isConfigurationChanged ( true );
2024-06-30 09:30:31 +00:00
if ( $instantDeploy ) {
$deployment_uuid = new Cuid2 ( 7 );
queue_application_deployment (
application : $application ,
deployment_uuid : $deployment_uuid ,
no_questions_asked : true ,
is_api : true ,
);
2024-07-02 14:12:04 +00:00
} else {
if ( $application -> build_pack === 'dockercompose' ) {
LoadComposeFile :: dispatch ( $application );
}
2024-06-30 09:30:31 +00:00
}
2024-07-03 15:10:00 +00:00
return response () -> json ( serializeApiResponse ([
'uuid' => data_get ( $application , 'uuid' ),
'domains' => data_get ( $application , 'domains' ),
]));
2024-06-30 09:30:31 +00:00
} elseif ( $type === 'private-deploy-key' ) {
2024-07-01 09:39:10 +00:00
if ( ! $request -> has ( 'name' )) {
$request -> offsetSet ( 'name' , generate_application_name ( $request -> git_repository , $request -> git_branch ));
}
2024-07-11 08:17:20 +00:00
if ( $request -> build_pack === 'dockercompose' ) {
$request -> offsetSet ( 'ports_exposes' , '80' );
}
2024-06-30 09:30:31 +00:00
$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' ,
2024-07-02 14:12:04 +00:00
'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' ,
2024-06-30 09:30:31 +00:00
]);
2024-07-02 14:12:04 +00:00
2024-06-30 09:30:31 +00:00
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 ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Private Key not found.' ], 404 );
2024-06-30 09:30:31 +00:00
}
$application = new Application ();
2024-07-01 14:26:50 +00:00
removeUnnecessaryFieldsFromRequest ( $request );
2024-06-30 09:30:31 +00:00
$application -> fill ( $request -> all ());
2024-07-02 14:12:04 +00:00
$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 ;
}
2024-06-30 09:30:31 +00:00
$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 ();
2024-07-03 15:10:00 +00:00
$application -> refresh ();
2024-07-17 12:52:40 +00:00
if ( ! $application -> settings -> is_container_label_readonly_enabled ) {
$application -> custom_labels = str ( implode ( '|coolify|' , generateLabelsApplication ( $application ))) -> replace ( '|coolify|' , " \n " );
$application -> save ();
}
2024-07-03 15:10:00 +00:00
$application -> isConfigurationChanged ( true );
2024-06-30 09:30:31 +00:00
if ( $instantDeploy ) {
$deployment_uuid = new Cuid2 ( 7 );
queue_application_deployment (
application : $application ,
deployment_uuid : $deployment_uuid ,
no_questions_asked : true ,
is_api : true ,
);
2024-07-02 14:12:04 +00:00
} else {
if ( $application -> build_pack === 'dockercompose' ) {
LoadComposeFile :: dispatch ( $application );
}
2024-06-30 09:30:31 +00:00
}
2024-07-03 15:10:00 +00:00
return response () -> json ( serializeApiResponse ([
'uuid' => data_get ( $application , 'uuid' ),
'domains' => data_get ( $application , 'domains' ),
]));
2024-07-01 09:39:10 +00:00
} 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 );
2024-07-01 14:26:50 +00:00
removeUnnecessaryFieldsFromRequest ( $request );
2024-07-01 09:39:10 +00:00
$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 ();
2024-07-03 15:10:00 +00:00
$application -> refresh ();
2024-07-17 12:52:40 +00:00
if ( ! $application -> settings -> is_container_label_readonly_enabled ) {
$application -> custom_labels = str ( implode ( '|coolify|' , generateLabelsApplication ( $application ))) -> replace ( '|coolify|' , " \n " );
$application -> save ();
}
2024-07-03 15:10:00 +00:00
$application -> isConfigurationChanged ( true );
2024-07-01 09:39:10 +00:00
if ( $instantDeploy ) {
$deployment_uuid = new Cuid2 ( 7 );
queue_application_deployment (
application : $application ,
deployment_uuid : $deployment_uuid ,
no_questions_asked : true ,
is_api : true ,
);
}
2024-07-03 15:10:00 +00:00
return response () -> json ( serializeApiResponse ([
'uuid' => data_get ( $application , 'uuid' ),
'domains' => data_get ( $application , 'domains' ),
]));
2024-07-09 11:19:21 +00:00
} elseif ( $type === 'dockerimage' ) {
2024-07-01 14:26:50 +00:00
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 ();
2024-07-03 15:10:00 +00:00
$application -> refresh ();
2024-07-17 12:52:40 +00:00
if ( ! $application -> settings -> is_container_label_readonly_enabled ) {
$application -> custom_labels = str ( implode ( '|coolify|' , generateLabelsApplication ( $application ))) -> replace ( '|coolify|' , " \n " );
$application -> save ();
}
2024-07-03 15:10:00 +00:00
$application -> isConfigurationChanged ( true );
2024-07-01 14:26:50 +00:00
if ( $instantDeploy ) {
$deployment_uuid = new Cuid2 ( 7 );
queue_application_deployment (
application : $application ,
deployment_uuid : $deployment_uuid ,
no_questions_asked : true ,
is_api : true ,
);
}
2024-07-03 15:10:00 +00:00
return response () -> json ( serializeApiResponse ([
'uuid' => data_get ( $application , 'uuid' ),
'domains' => data_get ( $application , 'domains' ),
]));
2024-07-02 14:12:04 +00:00
} 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 );
}
2024-07-01 14:26:50 +00:00
if ( ! $request -> has ( 'name' )) {
$request -> offsetSet ( 'name' , 'service' . new Cuid2 ( 7 ));
}
$validator = customApiValidator ( $request -> all (), [
sharedDataApplications (),
2024-07-02 14:12:04 +00:00
'docker_compose_raw' => 'string|required' ,
2024-07-01 14:26:50 +00:00
]);
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 ;
}
2024-07-02 14:12:04 +00:00
if ( ! isBase64Encoded ( $request -> docker_compose_raw )) {
2024-07-01 14:26:50 +00:00
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => [
2024-07-02 14:12:04 +00:00
'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.' ,
2024-07-01 14:26:50 +00:00
],
], 422 );
}
2024-07-02 14:12:04 +00:00
$dockerComposeRaw = base64_decode ( $request -> docker_compose_raw );
if ( mb_detect_encoding ( $dockerComposeRaw , 'ASCII' , true ) === false ) {
2024-07-01 14:26:50 +00:00
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => [
2024-07-02 14:12:04 +00:00
'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.' ,
2024-07-01 14:26:50 +00:00
],
], 422 );
}
2024-07-02 14:12:04 +00:00
$dockerCompose = base64_decode ( $request -> docker_compose_raw );
2024-07-01 14:26:50 +00:00
$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 );
2024-07-02 14:12:04 +00:00
$service -> fill ( $request -> all ());
2024-07-01 14:26:50 +00:00
$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 );
2024-07-09 11:19:21 +00:00
if ( $instantDeploy ) {
StartService :: dispatch ( $service );
}
2024-07-01 14:26:50 +00:00
2024-07-03 15:10:00 +00:00
return response () -> json ( serializeApiResponse ([
'uuid' => data_get ( $service , 'uuid' ),
2024-07-04 11:45:06 +00:00
'domains' => data_get ( $service , 'domains' ),
2024-07-03 15:10:00 +00:00
]));
2024-06-28 13:05:37 +00:00
}
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Invalid type.' ], 400 );
2024-06-28 13:05:37 +00:00
}
2024-07-09 08:45:10 +00:00
#[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 ,
2024-07-09 11:19:21 +00:00
description : 'Get application by UUID.' ,
2024-07-09 08:45:10 +00:00
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' ,
),
]
)]
2024-06-21 14:46:13 +00:00
public function application_by_uuid ( Request $request )
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-21 14:46:13 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
2024-06-21 14:46:13 +00:00
}
$uuid = $request -> route ( 'uuid' );
if ( ! $uuid ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'UUID is required.' ], 400 );
2024-07-01 14:26:50 +00:00
}
2024-06-26 11:00:36 +00:00
$application = Application :: ownedByCurrentTeamAPI ( $teamId ) -> where ( 'uuid' , $request -> uuid ) -> first ();
2024-06-21 14:46:13 +00:00
if ( ! $application ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Application not found.' ], 404 );
2024-06-21 14:46:13 +00:00
}
2024-07-03 11:13:38 +00:00
return response () -> json ( $this -> removeSensitiveData ( $application ));
2024-06-26 11:00:36 +00:00
}
2024-07-09 11:19:21 +00:00
#[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' ,
)
),
2024-07-11 10:38:54 +00:00
new OA\Parameter (
name : 'cleanup' ,
in : 'query' ,
description : 'Delete configurations and volumes.' ,
required : false ,
schema : new OA\Schema (
type : 'boolean' ,
default : true ,
)
),
2024-07-09 11:19:21 +00:00
],
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' ,
),
]
)]
2024-06-26 11:00:36 +00:00
public function delete_by_uuid ( Request $request )
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-07-11 10:50:12 +00:00
$cleanup = filter_var ( $request -> query -> get ( 'cleanup' , true ), FILTER_VALIDATE_BOOLEAN );
2024-06-26 11:00:36 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
2024-06-26 11:00:36 +00:00
}
2024-07-11 08:17:20 +00:00
if ( ! $request -> uuid ) {
return response () -> json ([ 'message' => 'UUID is required.' ], 404 );
2024-06-26 11:00:36 +00:00
}
$application = Application :: ownedByCurrentTeamAPI ( $teamId ) -> where ( 'uuid' , $request -> uuid ) -> first ();
if ( ! $application ) {
return response () -> json ([
'message' => 'Application not found' ,
], 404 );
}
2024-07-11 10:50:12 +00:00
DeleteResourceJob :: dispatch (
resource : $application ,
deleteConfigurations : $cleanup ,
deleteVolumes : $cleanup );
2024-06-26 11:00:36 +00:00
return response () -> json ([
'message' => 'Application deletion request queued.' ,
]);
2024-06-21 14:46:13 +00:00
}
2024-07-09 11:19:21 +00:00
#[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 : [
2024-07-09 12:12:36 +00:00
'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.' ],
2024-07-09 11:19:21 +00:00
],
)),
]),
responses : [
new OA\Response (
response : 200 ,
description : 'Application updated.' ,
2024-07-10 08:29:52 +00:00
content : [
new OA\MediaType (
mediaType : 'application/json' ,
schema : new OA\Schema (
type : 'object' ,
properties : [
'uuid' => [ 'type' => 'string' ],
]
)
),
]),
2024-07-09 11:19:21 +00:00
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' ,
),
]
)]
2024-06-21 19:35:02 +00:00
public function update_by_uuid ( Request $request )
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-21 19:35:02 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
2024-06-21 19:35:02 +00:00
}
if ( $request -> collect () -> count () == 0 ) {
return response () -> json ([
2024-06-25 19:22:23 +00:00
'message' => 'Invalid request.' ,
2024-06-21 19:35:02 +00:00
], 400 );
}
2024-07-01 14:26:50 +00:00
$return = validateIncomingRequest ( $request );
if ( $return instanceof \Illuminate\Http\JsonResponse ) {
return $return ;
}
2024-06-26 11:00:36 +00:00
$application = Application :: ownedByCurrentTeamAPI ( $teamId ) -> where ( 'uuid' , $request -> uuid ) -> first ();
2024-06-21 19:35:02 +00:00
if ( ! $application ) {
return response () -> json ([
'message' => 'Application not found' ,
], 404 );
}
2024-06-25 19:22:23 +00:00
$server = $application -> destination -> server ;
2024-07-09 08:45:10 +00:00
$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' ];
2024-06-26 11:32:36 +00:00
$validator = customApiValidator ( $request -> all (), [
2024-06-28 13:05:37 +00:00
sharedDataApplications (),
2024-06-25 13:05:51 +00:00
'name' => 'string|max:255' ,
2024-06-25 19:22:23 +00:00
'description' => 'string|nullable' ,
'static_image' => 'string' ,
'watch_paths' => 'string|nullable' ,
'docker_compose_location' => 'string' ,
'docker_compose_raw' => 'string|nullable' ,
2024-07-02 14:12:04 +00:00
'docker_compose_domains' => 'array|nullable' ,
2024-06-25 19:22:23 +00:00
'docker_compose_custom_start_command' => 'string|nullable' ,
'docker_compose_custom_build_command' => 'string|nullable' ,
2024-06-25 13:05:51 +00:00
]);
2024-06-25 19:22:23 +00:00
// Validate ports_exposes
if ( $request -> has ( 'ports_exposes' )) {
$ports = explode ( ',' , $request -> ports_exposes );
foreach ( $ports as $port ) {
if ( ! is_numeric ( $port )) {
return response () -> json ([
2024-06-26 11:00:36 +00:00
'message' => 'Validation failed.' ,
2024-06-25 19:22:23 +00:00
'errors' => [
'ports_exposes' => 'The ports_exposes should be a comma separated list of numbers.' ,
],
], 422 );
}
}
}
2024-06-30 09:30:31 +00:00
$return = $this -> validateDataApplications ( $request , $server );
2024-06-28 13:05:37 +00:00
if ( $return instanceof \Illuminate\Http\JsonResponse ) {
return $return ;
2024-06-25 19:22:23 +00:00
}
2024-06-25 13:05:51 +00:00
$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 ([
2024-06-26 11:00:36 +00:00
'message' => 'Validation failed.' ,
2024-06-25 13:05:51 +00:00
'errors' => $errors ,
], 422 );
}
2024-06-28 13:05:37 +00:00
$domains = $request -> domains ;
2024-06-25 19:22:23 +00:00
if ( $request -> has ( 'domains' ) && $server -> isProxyShouldRun ()) {
2024-07-10 08:29:52 +00:00
$errors = [];
2024-06-25 13:05:51 +00:00
$fqdn = $request -> domains ;
$fqdn = str ( $fqdn ) -> replaceEnd ( ',' , '' ) -> trim ();
$fqdn = str ( $fqdn ) -> replaceStart ( ',' , '' ) -> trim ();
$application -> fqdn = $fqdn ;
2024-07-17 12:52:40 +00:00
if ( ! $application -> settings -> is_container_label_readonly_enabled ) {
$customLabels = str ( implode ( '|coolify|' , generateLabelsApplication ( $application ))) -> replace ( '|coolify|' , " \n " );
$application -> custom_labels = base64_encode ( $customLabels );
}
2024-06-25 13:05:51 +00:00
$request -> offsetUnset ( 'domains' );
}
2024-06-28 13:05:37 +00:00
2024-07-02 14:12:04 +00:00
$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' );
}
2024-06-28 13:05:37 +00:00
$data = $request -> all ();
data_set ( $data , 'fqdn' , $domains );
2024-07-02 14:12:04 +00:00
if ( $dockerComposeDomainsJson -> count () > 0 ) {
data_set ( $data , 'docker_compose_domains' , json_encode ( $dockerComposeDomainsJson ));
}
2024-06-28 13:05:37 +00:00
$application -> fill ( $data );
2024-06-25 13:05:51 +00:00
$application -> save ();
2024-06-21 19:35:02 +00:00
2024-07-10 08:29:52 +00:00
return response () -> json ([
'uuid' => $application -> uuid ,
]);
2024-06-26 11:00:36 +00:00
}
2024-07-09 11:19:21 +00:00
#[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' ,
),
]
)]
2024-07-04 11:45:06 +00:00
public function envs ( Request $request )
2024-06-26 11:00:36 +00:00
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-26 11:00:36 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
}
2024-06-26 11:00:36 +00:00
$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' ));
2024-07-04 11:45:06 +00:00
$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 );
2024-06-26 11:00:36 +00:00
}
2024-07-09 11:19:21 +00:00
#[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 : [
2024-07-09 12:12:36 +00:00
'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.' ],
2024-07-09 11:19:21 +00:00
],
),
),
],
),
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' ,
),
]
)]
2024-06-26 11:00:36 +00:00
public function update_env_by_uuid ( Request $request )
{
2024-06-26 11:57:04 +00:00
$allowedFields = [ 'key' , 'value' , 'is_preview' , 'is_build_time' , 'is_literal' ];
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-26 11:00:36 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
}
$return = validateIncomingRequest ( $request );
if ( $return instanceof \Illuminate\Http\JsonResponse ) {
return $return ;
2024-06-26 11:00:36 +00:00
}
$application = Application :: ownedByCurrentTeamAPI ( $teamId ) -> where ( 'uuid' , $request -> uuid ) -> first ();
if ( ! $application ) {
return response () -> json ([
'message' => 'Application not found' ,
], 404 );
}
2024-06-26 11:32:36 +00:00
$validator = customApiValidator ( $request -> all (), [
2024-06-26 11:00:36 +00:00
'key' => 'string|required' ,
'value' => 'string|nullable' ,
'is_preview' => 'boolean' ,
'is_build_time' => 'boolean' ,
'is_literal' => 'boolean' ,
2024-07-04 11:45:06 +00:00
'is_multiline' => 'boolean' ,
'is_shown_once' => 'boolean' ,
2024-06-26 11:00:36 +00:00
]);
$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 ;
}
2024-07-04 11:45:06 +00:00
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 ;
}
2024-06-26 11:00:36 +00:00
$env -> save ();
2024-07-09 11:19:21 +00:00
return response () -> json ( $this -> removeSensitiveData ( $env )) -> setStatusCode ( 201 );
2024-06-26 11:00:36 +00:00
} 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 ;
}
2024-07-04 11:45:06 +00:00
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 ;
}
2024-06-26 11:00:36 +00:00
$env -> save ();
2024-07-09 11:19:21 +00:00
return response () -> json ( $this -> removeSensitiveData ( $env )) -> setStatusCode ( 201 );
2024-06-26 11:00:36 +00:00
} else {
return response () -> json ([
'message' => 'Environment variable not found.' ,
], 404 );
}
}
return response () -> json ([
2024-07-03 11:13:38 +00:00
'message' => 'Something is not okay. Are you okay?' ,
2024-06-26 11:00:36 +00:00
], 500 );
}
2024-07-09 11:19:21 +00:00
#[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 : [
2024-07-09 12:12:36 +00:00
'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.' ],
2024-07-09 11:19:21 +00:00
],
),
],
],
),
),
],
),
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' ,
),
]
)]
2024-06-26 11:32:36 +00:00
public function create_bulk_envs ( Request $request )
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-26 11:32:36 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
}
$return = validateIncomingRequest ( $request );
if ( $return instanceof \Illuminate\Http\JsonResponse ) {
return $return ;
2024-06-26 11:32:36 +00:00
}
$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 ) {
2024-06-26 11:57:04 +00:00
return collect ( $item ) -> only ([ 'key' , 'value' , 'is_preview' , 'is_build_time' , 'is_literal' ]);
2024-06-26 11:32:36 +00:00
});
foreach ( $bulk_data as $item ) {
$validator = customApiValidator ( $item , [
'key' => 'string|required' ,
'value' => 'string|nullable' ,
'is_preview' => 'boolean' ,
'is_build_time' => 'boolean' ,
'is_literal' => 'boolean' ,
2024-07-04 11:45:06 +00:00
'is_multiline' => 'boolean' ,
'is_shown_once' => 'boolean' ,
2024-06-26 11:32:36 +00:00
]);
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 ;
2024-07-04 11:45:06 +00:00
$is_multi_line = $item -> get ( 'is_multiline' ) ? ? false ;
$is_shown_once = $item -> get ( 'is_shown_once' ) ? ? false ;
2024-06-26 11:32:36 +00:00
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 ;
}
2024-07-04 11:45:06 +00:00
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' );
}
2024-06-26 11:32:36 +00:00
$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 ,
2024-07-04 11:45:06 +00:00
'is_multiline' => $is_multi_line ,
'is_shown_once' => $is_shown_once ,
2024-06-26 11:32:36 +00:00
]);
}
} 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 ;
}
2024-07-04 11:45:06 +00:00
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' );
}
2024-06-26 11:32:36 +00:00
$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 ,
2024-07-04 11:45:06 +00:00
'is_multiline' => $is_multi_line ,
'is_shown_once' => $is_shown_once ,
2024-06-26 11:32:36 +00:00
]);
}
}
}
2024-07-09 11:19:21 +00:00
return response () -> json ( $this -> removeSensitiveData ( $env )) -> setStatusCode ( 201 );
2024-06-26 11:32:36 +00:00
}
2024-07-09 11:19:21 +00:00
#[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 : [
2024-07-09 12:12:36 +00:00
'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.' ],
2024-07-09 11:19:21 +00:00
],
),
),
),
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' ,
),
]
)]
2024-06-26 11:00:36 +00:00
public function create_env ( Request $request )
{
$allowedFields = [ 'key' , 'value' , 'is_preview' , 'is_build_time' , 'is_literal' ];
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-26 11:00:36 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
2024-06-26 11:00:36 +00:00
}
$application = Application :: ownedByCurrentTeamAPI ( $teamId ) -> where ( 'uuid' , $request -> uuid ) -> first ();
if ( ! $application ) {
return response () -> json ([
'message' => 'Application not found' ,
], 404 );
}
2024-06-26 11:32:36 +00:00
$validator = customApiValidator ( $request -> all (), [
2024-06-26 11:00:36 +00:00
'key' => 'string|required' ,
'value' => 'string|nullable' ,
'is_preview' => 'boolean' ,
'is_build_time' => 'boolean' ,
'is_literal' => 'boolean' ,
2024-07-04 11:45:06 +00:00
'is_multiline' => 'boolean' ,
'is_shown_once' => 'boolean' ,
2024-06-26 11:00:36 +00:00
]);
$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 ,
2024-07-04 11:45:06 +00:00
'is_multiline' => $request -> is_multiline ? ? false ,
'is_shown_once' => $request -> is_shown_once ? ? false ,
2024-06-26 11:00:36 +00:00
]);
2024-07-09 11:19:21 +00:00
return response () -> json ([
'uuid' => $env -> uuid ,
]) -> setStatusCode ( 201 );
2024-06-26 11:00:36 +00:00
}
} 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 ,
2024-07-04 11:45:06 +00:00
'is_multiline' => $request -> is_multiline ? ? false ,
'is_shown_once' => $request -> is_shown_once ? ? false ,
2024-06-26 11:00:36 +00:00
]);
2024-07-09 11:19:21 +00:00
return response () -> json ([
'uuid' => $env -> uuid ,
]) -> setStatusCode ( 201 );
2024-06-26 11:00:36 +00:00
}
}
return response () -> json ([
'message' => 'Something went wrong.' ,
], 500 );
}
2024-07-09 11:19:21 +00:00
#[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' ,
),
]
)]
2024-06-26 11:00:36 +00:00
public function delete_env_by_uuid ( Request $request )
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-26 11:00:36 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
2024-06-26 11:00:36 +00:00
}
$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 );
}
2024-07-02 14:12:04 +00:00
$found_env -> forceDelete ();
2024-06-26 11:00:36 +00:00
2024-06-21 19:35:02 +00:00
return response () -> json ([
2024-06-26 11:00:36 +00:00
'message' => 'Environment variable deleted.' ,
2024-06-21 19:35:02 +00:00
]);
}
2024-07-09 11:19:21 +00:00
#[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 : [
2024-07-09 12:12:36 +00:00
'message' => [ 'type' => 'string' , 'example' => 'Deployment request queued.' , 'description' => 'Message.' ],
'deployment_uuid' => [ 'type' => 'string' , 'example' => 'doogksw' , 'description' => 'UUID of the deployment.' ],
2024-07-09 11:19:21 +00:00
])
),
]),
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' ,
),
]
)]
2024-06-21 14:46:13 +00:00
public function action_deploy ( Request $request )
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-21 14:46:13 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
2024-06-21 14:46:13 +00:00
}
$force = $request -> query -> get ( 'force' ) ? ? false ;
$instant_deploy = $request -> query -> get ( 'instant_deploy' ) ? ? false ;
$uuid = $request -> route ( 'uuid' );
if ( ! $uuid ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'UUID is required.' ], 400 );
2024-06-21 14:46:13 +00:00
}
2024-06-26 11:00:36 +00:00
$application = Application :: ownedByCurrentTeamAPI ( $teamId ) -> where ( 'uuid' , $request -> uuid ) -> first ();
2024-06-21 14:46:13 +00:00
if ( ! $application ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Application not found.' ], 404 );
2024-06-21 14:46:13 +00:00
}
$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.' ,
2024-07-04 11:45:06 +00:00
'deployment_uuid' => $deployment_uuid -> toString (),
2024-06-21 14:46:13 +00:00
],
200
);
}
2024-07-09 11:19:21 +00:00
#[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' ,
),
]
)]
2024-06-21 14:46:13 +00:00
public function action_stop ( Request $request )
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-21 14:46:13 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
2024-06-21 14:46:13 +00:00
}
$uuid = $request -> route ( 'uuid' );
if ( ! $uuid ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'UUID is required.' ], 400 );
2024-06-21 14:46:13 +00:00
}
2024-06-26 11:00:36 +00:00
$application = Application :: ownedByCurrentTeamAPI ( $teamId ) -> where ( 'uuid' , $request -> uuid ) -> first ();
2024-06-21 14:46:13 +00:00
if ( ! $application ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Application not found.' ], 404 );
2024-06-21 14:46:13 +00:00
}
2024-07-02 14:12:04 +00:00
StopApplication :: dispatch ( $application );
2024-06-21 14:46:13 +00:00
2024-07-02 14:12:04 +00:00
return response () -> json (
[
'message' => 'Application stopping request queued.' ,
],
);
2024-06-21 14:46:13 +00:00
}
2024-07-09 11:19:21 +00:00
#[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.' ],
2024-07-09 12:12:36 +00:00
'deployment_uuid' => [ 'type' => 'string' , 'example' => 'doogksw' , 'description' => 'UUID of the deployment.' ],
2024-07-09 11:19:21 +00:00
]
)
),
]),
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' ,
),
]
)]
2024-06-21 14:46:13 +00:00
public function action_restart ( Request $request )
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-21 14:46:13 +00:00
if ( is_null ( $teamId )) {
2024-07-01 14:26:50 +00:00
return invalidTokenResponse ();
2024-06-21 14:46:13 +00:00
}
$uuid = $request -> route ( 'uuid' );
if ( ! $uuid ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'UUID is required.' ], 400 );
2024-06-21 14:46:13 +00:00
}
2024-06-26 11:00:36 +00:00
$application = Application :: ownedByCurrentTeamAPI ( $teamId ) -> where ( 'uuid' , $request -> uuid ) -> first ();
2024-06-21 14:46:13 +00:00
if ( ! $application ) {
2024-07-03 11:13:38 +00:00
return response () -> json ([ 'message' => 'Application not found.' ], 404 );
2024-06-21 14:46:13 +00:00
}
$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.' ,
2024-07-04 11:45:06 +00:00
'deployment_uuid' => $deployment_uuid -> toString (),
2024-06-21 14:46:13 +00:00
],
);
}
2024-06-30 09:30:31 +00:00
private function validateDataApplications ( Request $request , Server $server )
{
2024-07-01 14:26:50 +00:00
$teamId = getTeamIdFromToken ();
2024-06-30 09:30:31 +00:00
// 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 ()) {
2024-07-10 08:29:52 +00:00
$uuid = $request -> uuid ;
2024-06-30 09:30:31 +00:00
$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 );
}
2024-07-10 08:29:52 +00:00
if ( checkIfDomainIsAlreadyUsed ( $fqdn , $teamId , $uuid )) {
2024-07-01 09:39:10 +00:00
return response () -> json ([
'message' => 'Validation failed.' ,
'errors' => [
'domains' => 'One of the domain is already used.' ,
],
], 422 );
}
2024-06-30 09:30:31 +00:00
}
}
2024-06-21 14:46:13 +00:00
}