diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index 1db2a3663..2c831879b 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -7,7 +7,6 @@ use App\Actions\Service\StartService; use App\Actions\Service\StopService; use App\Http\Controllers\Controller; use App\Jobs\DeleteResourceJob; -use App\Models\Environment; use App\Models\EnvironmentVariable; use App\Models\Project; use App\Models\Server; @@ -378,14 +377,118 @@ class ServicesController extends Controller return response()->json(['message' => 'Service not found.', 'valid_service_types' => $serviceKeys], 404); } elseif (filled($request->docker_compose_raw)) { + $allowedFields = ['name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'docker_compose_raw', 'connect_to_docker_network']; - $service = new Service; - $result = $this->upsert_service($request, $service, $teamId); - if ($result instanceof \Illuminate\Http\JsonResponse) { - return $result; + $validator = customApiValidator($request->all(), [ + 'project_uuid' => 'string|required', + 'environment_name' => 'string|nullable', + 'environment_uuid' => 'string|nullable', + 'server_uuid' => 'string|required', + 'destination_uuid' => 'string', + 'name' => 'string|max:255', + 'description' => 'string|nullable', + 'instant_deploy' => 'boolean', + 'connect_to_docker_network' => 'boolean', + 'docker_compose_raw' => 'string|required', + ]); + + $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); } - return response()->json(serializeApiResponse($result))->setStatusCode(201); + $environmentUuid = $request->environment_uuid; + $environmentName = $request->environment_name; + if (blank($environmentUuid) && blank($environmentName)) { + return response()->json(['message' => 'You need to provide at least one of environment_name or environment_uuid.'], 422); + } + $serverUuid = $request->server_uuid; + $projectUuid = $request->project_uuid; + $project = Project::whereTeamId($teamId)->whereUuid($projectUuid)->first(); + if (! $project) { + return response()->json(['message' => 'Project not found.'], 404); + } + $environment = $project->environments()->where('name', $environmentName)->first(); + if (! $environment) { + $environment = $project->environments()->where('uuid', $environmentUuid)->first(); + } + if (! $environment) { + return response()->json(['message' => 'Environment not found.'], 404); + } + $server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first(); + if (! $server) { + return response()->json(['message' => 'Server not found.'], 404); + } + $destinations = $server->destinations(); + if ($destinations->count() == 0) { + return response()->json(['message' => 'Server has no destinations.'], 400); + } + if ($destinations->count() > 1 && ! $request->has('destination_uuid')) { + return response()->json(['message' => 'Server has multiple destinations and you do not set destination_uuid.'], 400); + } + $destination = $destinations->first(); + if (! isBase64Encoded($request->docker_compose_raw)) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.', + ], + ], 422); + } + $dockerComposeRaw = base64_decode($request->docker_compose_raw); + if (mb_detect_encoding($dockerComposeRaw, 'ASCII', true) === false) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'docker_compose_raw' => 'The docker_compose_raw should be base64 encoded.', + ], + ], 422); + } + $dockerCompose = base64_decode($request->docker_compose_raw); + $dockerComposeRaw = Yaml::dump(Yaml::parse($dockerCompose), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK); + + $connectToDockerNetwork = $request->connect_to_docker_network ?? false; + $instantDeploy = $request->instant_deploy ?? false; + + $service = new Service; + $service->name = $request->name ?? 'service-'.str()->random(10); + $service->description = $request->description; + $service->docker_compose_raw = $dockerComposeRaw; + $service->environment_id = $environment->id; + $service->server_id = $server->id; + $service->destination_id = $destination->id; + $service->destination_type = $destination->getMorphClass(); + $service->connect_to_docker_network = $connectToDockerNetwork; + $service->save(); + + $service->parse(isNew: true); + if ($instantDeploy) { + StartService::dispatch($service); + } + + $domains = $service->applications()->get()->pluck('fqdn')->sort(); + $domains = $domains->map(function ($domain) { + if (count(explode(':', $domain)) > 2) { + return str($domain)->beforeLast(':')->value(); + } + + return $domain; + })->values(); + + return response()->json([ + 'uuid' => $service->uuid, + 'domains' => $domains, + ])->setStatusCode(201); } else { return response()->json(['message' => 'No service type or docker_compose_raw provided.'], 400); } @@ -615,23 +718,9 @@ class ServicesController extends Controller return response()->json(['message' => 'Service not found.'], 404); } - $result = $this->upsert_service($request, $service, $teamId); - if ($result instanceof \Illuminate\Http\JsonResponse) { - return $result; - } + $allowedFields = ['name', 'description', 'instant_deploy', 'docker_compose_raw', 'connect_to_docker_network']; - return response()->json(serializeApiResponse($result))->setStatusCode(200); - } - - private function upsert_service(Request $request, Service $service, string $teamId) - { - $allowedFields = ['name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'docker_compose_raw', 'connect_to_docker_network']; $validator = customApiValidator($request->all(), [ - 'project_uuid' => 'string|nullable', - 'environment_name' => 'string|nullable', - 'environment_uuid' => 'string|nullable', - 'server_uuid' => 'string|nullable', - 'destination_uuid' => 'string', 'name' => 'string|max:255', 'description' => 'string|nullable', 'instant_deploy' => 'boolean', @@ -653,42 +742,6 @@ class ServicesController extends Controller 'errors' => $errors, ], 422); } - - $environmentUuid = null; - $environmentName = null; - - if ($request->environment_uuid) { - $environmentUuid = $request->environment_uuid; - } elseif ($request->environment_name) { - $environmentName = $request->environment_name; - } else { - $environmentUuid = $service->environment->uuid; - } - $serverUuid = $request->server_uuid ?? $service->server->uuid; - $projectUuid = $request->project_uuid ?? $service->environment->project->uuid; - $project = Project::whereTeamId($teamId)->whereUuid($projectUuid)->first(); - if (! $project) { - return response()->json(['message' => 'Project not found.'], 404); - } - $environment = $project->environments()->where('name', $environmentName)->first(); - if (! $environment) { - $environment = $project->environments()->where('uuid', $environmentUuid)->first(); - } - if (! $environment) { - return response()->json(['message' => 'Environment not found.'], 404); - } - $server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first(); - if (! $server) { - return response()->json(['message' => 'Server not found.'], 404); - } - $destinations = $server->destinations(); - if ($destinations->count() == 0) { - return response()->json(['message' => 'Server has no destinations.'], 400); - } - if ($destinations->count() > 1 && ! $request->has('destination_uuid')) { - return response()->json(['message' => 'Server has multiple destinations and you do not set destination_uuid.'], 400); - } - $destination = $destinations->first(); if ($request->has('docker_compose_raw')) { if (! isBase64Encoded($request->docker_compose_raw)) { return response()->json([ @@ -711,17 +764,18 @@ class ServicesController extends Controller $dockerComposeRaw = Yaml::dump(Yaml::parse($dockerCompose), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK); $service->docker_compose_raw = $dockerComposeRaw; } - $connectToDockerNetwork = $request->connect_to_docker_network ?? false; - $service->name = $request->name ?? $service->name; - $service->description = $request->description ?? $service->description; - $service->environment_id = $environment->id; - $service->server_id = $server->id; - $service->destination_id = $destination->id; - $service->destination_type = $destination->getMorphClass(); - $service->connect_to_docker_network = $connectToDockerNetwork; + if ($request->has('name')) { + $service->name = $request->name; + } + if ($request->has('description')) { + $service->description = $request->description; + } + if ($request->has('connect_to_docker_network')) { + $service->connect_to_docker_network = $request->connect_to_docker_network; + } $service->save(); - + $service->parse(); if ($request->instant_deploy) { StartService::dispatch($service); @@ -736,10 +790,10 @@ class ServicesController extends Controller return $domain; })->values(); - return [ + return response()->json([ 'uuid' => $service->uuid, 'domains' => $domains, - ]; + ])->setStatusCode(200); } #[OA\Get(