feat: lots of api endpoints

This commit is contained in:
Andras Bacsai 2024-07-01 16:26:50 +02:00
parent dbc235d84a
commit da6f2da3d0
30 changed files with 1583 additions and 417 deletions

View File

@ -0,0 +1,15 @@
<?php
namespace App\Enums;
enum NewDatabaseTypes: string
{
case POSTGRESQL = 'postgresql';
case MYSQL = 'mysql';
case MONGODB = 'mongodb';
case REDIS = 'redis';
case MARIADB = 'mariadb';
case KEYDB = 'keydb';
case DRAGONFLY = 'dragonfly';
case CLICKHOUSE = 'clickhouse';
}

View File

@ -20,7 +20,7 @@ public function __construct($userId = null)
$userId = auth()->user()->id ?? null;
}
if (is_null($userId)) {
throw new \Exception('User id is null');
throw new \RuntimeException('User id is null');
}
$this->userId = $userId;
}

View File

@ -13,47 +13,45 @@
use App\Models\PrivateKey;
use App\Models\Project;
use App\Models\Server;
use App\Models\Service;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Symfony\Component\Yaml\Yaml;
use Visus\Cuid2\Cuid2;
class Applications extends Controller
class ApplicationsController extends Controller
{
public function applications(Request $request)
{
$teamId = get_team_id_from_token();
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalid_token();
return invalidTokenResponse();
}
$projects = Project::where('team_id', $teamId)->get();
$applications = collect();
$applications->push($projects->pluck('applications')->flatten());
$applications = $applications->flatten();
$applications = $applications->map(function ($application) {
return serializeApiResponse($application);
});
return response()->json(serialize_api_response($applications));
return response()->json([
'success' => true,
'data' => $applications,
]);
}
public function create_application(Request $request)
{
ray()->clearAll();
$allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile'];
$teamId = get_team_id_from_token();
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalid_token();
return invalidTokenResponse();
}
if (! $request->isJson()) {
return response()->json([
'message' => 'Invalid request.',
'error' => 'Content-Type must be application/json.',
], 400);
}
// check if request is valid json
if (! json_decode($request->getContent())) {
return response()->json([
'message' => 'Invalid request.',
'error' => 'Invalid JSON.',
], 400);
$return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return;
}
$validator = customApiValidator($request->all(), [
'name' => 'string|max:255',
@ -75,6 +73,7 @@ public function create_application(Request $request)
}
return response()->json([
'success' => false,
'message' => 'Validation failed.',
'errors' => $errors,
], 422);
@ -88,22 +87,22 @@ public function create_application(Request $request)
$project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first();
if (! $project) {
return response()->json(['error' => 'Project not found.'], 404);
return response()->json(['succes' => false, 'message' => 'Project not found.'], 404);
}
$environment = $project->environments()->where('name', $request->environment_name)->first();
if (! $environment) {
return response()->json(['error' => 'Environment not found.'], 404);
return response()->json(['success' => false, 'message' => 'Environment not found.'], 404);
}
$server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first();
if (! $server) {
return response()->json(['error' => 'Server not found.'], 404);
return response()->json(['success' => false, 'message' => 'Server not found.'], 404);
}
$destinations = $server->destinations();
if ($destinations->count() == 0) {
return response()->json(['error' => 'Server has no destinations.'], 400);
return response()->json(['success' => false, 'message' => 'Server has no destinations.'], 400);
}
if ($destinations->count() > 1 && ! $request->has('destination_uuid')) {
return response()->json(['error' => 'Server has multiple destinations and you do not set destination_uuid.'], 400);
return response()->json(['success' => false, 'message' => 'Server has multiple destinations and you do not set destination_uuid.'], 400);
}
$destination = $destinations->first();
if ($type === 'public') {
@ -114,11 +113,12 @@ public function create_application(Request $request)
sharedDataApplications(),
'git_repository' => 'string|required',
'git_branch' => 'string|required',
'build_pack' => ['required', Rule::enum(BuildPackTypes::class)],
'build_pack' => [Rule::enum(BuildPackTypes::class)],
'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required',
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => 'Validation failed.',
'errors' => $validator->errors(),
], 422);
@ -128,7 +128,7 @@ public function create_application(Request $request)
return $return;
}
$application = new Application();
$this->removeUnnecessaryFieldsFromRequest($request);
removeUnnecessaryFieldsFromRequest($request);
$application->fill($request->all());
@ -149,7 +149,10 @@ public function create_application(Request $request)
);
}
return response()->json(serialize_api_response($application));
return response()->json([
'success' => true,
'data' => serializeApiResponse($application),
]);
} elseif ($type === 'private-gh-app') {
if (! $request->has('name')) {
$request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
@ -164,6 +167,7 @@ public function create_application(Request $request)
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => 'Validation failed.',
'errors' => $validator->errors(),
], 422);
@ -174,14 +178,14 @@ public function create_application(Request $request)
}
$githubApp = GithubApp::whereTeamId($teamId)->where('uuid', $githubAppUuid)->first();
if (! $githubApp) {
return response()->json(['error' => 'Github App not found.'], 404);
return response()->json(['success' => false, 'message' => 'Github App not found.'], 404);
}
$gitRepository = $request->git_repository;
if (str($gitRepository)->startsWith('http') || str($gitRepository)->contains('github.com')) {
$gitRepository = str($gitRepository)->replace('https://', '')->replace('http://', '')->replace('github.com/', '');
}
$application = new Application();
$this->removeUnnecessaryFieldsFromRequest($request);
removeUnnecessaryFieldsFromRequest($request);
$application->fill($request->all());
@ -205,7 +209,10 @@ public function create_application(Request $request)
);
}
return response()->json(serialize_api_response($application));
return response()->json([
'success' => true,
'data' => serializeApiResponse($application),
]);
} elseif ($type === 'private-deploy-key') {
if (! $request->has('name')) {
$request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch));
@ -230,11 +237,11 @@ public function create_application(Request $request)
}
$privateKey = PrivateKey::whereTeamId($teamId)->where('uuid', $request->private_key_uuid)->first();
if (! $privateKey) {
return response()->json(['error' => 'Private Key not found.'], 404);
return response()->json(['success' => false, 'message' => 'Private Key not found.'], 404);
}
$application = new Application();
$this->removeUnnecessaryFieldsFromRequest($request);
removeUnnecessaryFieldsFromRequest($request);
$application->fill($request->all());
$application->fqdn = $fqdn;
@ -255,7 +262,10 @@ public function create_application(Request $request)
);
}
return response()->json(serialize_api_response($application));
return response()->json([
'success' => true,
'data' => serializeApiResponse($application),
]);
} elseif ($type === 'dockerfile') {
if (! $request->has('name')) {
$request->offsetSet('name', 'dockerfile-'.new Cuid2(7));
@ -266,6 +276,7 @@ public function create_application(Request $request)
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => 'Validation failed.',
'errors' => $validator->errors(),
], 422);
@ -276,6 +287,7 @@ public function create_application(Request $request)
}
if (! isBase64Encoded($request->dockerfile)) {
return response()->json([
'success' => false,
'message' => 'Validation failed.',
'errors' => [
'dockerfile' => 'The dockerfile should be base64 encoded.',
@ -285,6 +297,7 @@ public function create_application(Request $request)
$dockerFile = base64_decode($request->dockerfile);
if (mb_detect_encoding($dockerFile, 'ASCII', true) === false) {
return response()->json([
'success' => false,
'message' => 'Validation failed.',
'errors' => [
'dockerfile' => 'The dockerfile should be base64 encoded.',
@ -292,7 +305,7 @@ public function create_application(Request $request)
], 422);
}
$dockerFile = base64_decode($request->dockerfile);
$this->removeUnnecessaryFieldsFromRequest($request);
removeUnnecessaryFieldsFromRequest($request);
$port = get_port_from_dockerfile($request->dockerfile);
if (! $port) {
@ -324,42 +337,179 @@ public function create_application(Request $request)
);
}
return response()->json(serialize_api_response($application));
return response()->json([
'success' => true,
'data' => serializeApiResponse($application),
]);
} elseif ($type === 'docker-image') {
if (! $request->has('name')) {
$request->offsetSet('name', 'docker-image-'.new Cuid2(7));
}
$validator = customApiValidator($request->all(), [
sharedDataApplications(),
'docker_registry_image_name' => 'string|required',
'docker_registry_image_tag' => 'string',
'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required',
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => 'Validation failed.',
'errors' => $validator->errors(),
], 422);
}
$return = $this->validateDataApplications($request, $server);
if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return;
}
if (! $request->docker_registry_image_tag) {
$request->offsetSet('docker_registry_image_tag', 'latest');
}
$application = new Application();
removeUnnecessaryFieldsFromRequest($request);
$application->fill($request->all());
$application->fqdn = $fqdn;
$application->build_pack = 'dockerimage';
$application->destination_id = $destination->id;
$application->destination_type = $destination->getMorphClass();
$application->environment_id = $environment->id;
$application->git_repository = 'coollabsio/coolify';
$application->git_branch = 'main';
$application->save();
if ($instantDeploy) {
$deployment_uuid = new Cuid2(7);
queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
no_questions_asked: true,
is_api: true,
);
}
return response()->json([
'success' => true,
'data' => serializeApiResponse($application),
]);
} elseif ($type === 'docker-compose-empty') {
if (! $request->has('name')) {
$request->offsetSet('name', 'service'.new Cuid2(7));
}
$validator = customApiValidator($request->all(), [
sharedDataApplications(),
'docker_compose' => 'string|required',
'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required',
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => 'Validation failed.',
'errors' => $validator->errors(),
], 422);
}
$return = $this->validateDataApplications($request, $server);
if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return;
}
if (! isBase64Encoded($request->docker_compose)) {
return response()->json([
'success' => false,
'message' => 'Validation failed.',
'errors' => [
'docker_compose' => 'The docker_compose should be base64 encoded.',
],
], 422);
}
$dockerCompose = base64_decode($request->docker_compose);
if (mb_detect_encoding($dockerCompose, 'ASCII', true) === false) {
return response()->json([
'message' => 'Validation failed.',
'errors' => [
'docker_compose' => 'The docker_compose should be base64 encoded.',
],
], 422);
}
$dockerCompose = base64_decode($request->docker_compose);
$dockerComposeRaw = Yaml::dump(Yaml::parse($dockerCompose), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
// $isValid = validateComposeFile($dockerComposeRaw, $server_id);
// if ($isValid !== 'OK') {
// return $this->dispatch('error', "Invalid docker-compose file.\n$isValid");
// }
$service = new Service();
removeUnnecessaryFieldsFromRequest($request);
$service->name = $request->name;
$service->description = $request->description;
$service->docker_compose_raw = $dockerComposeRaw;
$service->environment_id = $environment->id;
$service->server_id = $server->id;
$service->destination_id = $destination->id;
$service->destination_type = $destination->getMorphClass();
$service->save();
$service->name = "service-$service->uuid";
$service->parse(isNew: true);
// if ($instantDeploy) {
// $deployment_uuid = new Cuid2(7);
// queue_application_deployment(
// application: $application,
// deployment_uuid: $deployment_uuid,
// no_questions_asked: true,
// is_api: true,
// );
// }
return response()->json([
'success' => true,
'data' => serializeApiResponse($service),
]);
}
return response()->json(['error' => 'Invalid type.'], 400);
return response()->json(['success' => false, 'message' => 'Invalid type.'], 400);
}
public function application_by_uuid(Request $request)
{
$teamId = get_team_id_from_token();
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalid_token();
return invalidTokenResponse();
}
$uuid = $request->route('uuid');
if (! $uuid) {
return response()->json(['error' => 'UUID is required.'], 400);
return response()->json(['success' => false, 'message' => 'UUID is required.'], 400);
}
$return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return;
}
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
if (! $application) {
return response()->json(['error' => 'Application not found.'], 404);
return response()->json(['success' => false, 'message' => 'Application not found.'], 404);
}
return response()->json(serialize_api_response($application));
return response()->json([
'success' => true,
'data' => serializeApiResponse($application),
]);
}
public function delete_by_uuid(Request $request)
{
ray()->clearAll();
$teamId = get_team_id_from_token();
$teamId = getTeamIdFromToken();
$cleanup = $request->query->get('cleanup') ?? false;
if (is_null($teamId)) {
return invalid_token();
return invalidTokenResponse();
}
if ($request->collect()->count() == 0) {
return response()->json([
'success' => false,
'message' => 'Invalid request.',
], 400);
}
@ -381,17 +531,21 @@ public function delete_by_uuid(Request $request)
public function update_by_uuid(Request $request)
{
ray()->clearAll();
$teamId = get_team_id_from_token();
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalid_token();
return invalidTokenResponse();
}
if ($request->collect()->count() == 0) {
return response()->json([
'success' => false,
'message' => 'Invalid request.',
], 400);
}
$return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return;
}
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
if (! $application) {
@ -423,6 +577,7 @@ public function update_by_uuid(Request $request)
foreach ($ports as $port) {
if (! is_numeric($port)) {
return response()->json([
'success' => false,
'message' => 'Validation failed.',
'errors' => [
'ports_exposes' => 'The ports_exposes should be a comma separated list of numbers.',
@ -445,6 +600,7 @@ public function update_by_uuid(Request $request)
}
return response()->json([
'success' => false,
'message' => 'Validation failed.',
'errors' => $errors,
], 422);
@ -467,15 +623,21 @@ public function update_by_uuid(Request $request)
$application->fill($data);
$application->save();
return response()->json(serialize_api_response($application));
return response()->json([
'success' => true,
'data' => serializeApiResponse($application),
]);
}
public function envs_by_uuid(Request $request)
{
ray()->clearAll();
$teamId = get_team_id_from_token();
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalid_token();
return invalidTokenResponse();
}
$return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return;
}
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
@ -487,17 +649,24 @@ public function envs_by_uuid(Request $request)
}
$envs = $application->environment_variables->sortBy('id')->merge($application->environment_variables_preview->sortBy('id'));
return response()->json(serialize_api_response($envs));
return response()->json([
'success' => true,
'data' => serializeApiResponse($envs),
]);
}
public function update_env_by_uuid(Request $request)
{
ray()->clearAll();
$allowedFields = ['key', 'value', 'is_preview', 'is_build_time', 'is_literal'];
$teamId = get_team_id_from_token();
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalid_token();
return invalidTokenResponse();
}
$return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return;
}
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
@ -525,6 +694,7 @@ public function update_env_by_uuid(Request $request)
}
return response()->json([
'success' => false,
'message' => 'Validation failed.',
'errors' => $errors,
], 422);
@ -547,9 +717,10 @@ public function update_env_by_uuid(Request $request)
}
$env->save();
return response()->json(serialize_api_response($env));
return response()->json(serializeApiResponse($env));
} else {
return response()->json([
'success' => false,
'message' => 'Environment variable not found.',
], 404);
}
@ -568,10 +739,14 @@ public function update_env_by_uuid(Request $request)
}
$env->save();
return response()->json(serialize_api_response($env));
return response()->json([
'success' => true,
'data' => serializeApiResponse($env),
]);
} else {
return response()->json([
'success' => false,
'message' => 'Environment variable not found.',
], 404);
@ -579,6 +754,7 @@ public function update_env_by_uuid(Request $request)
}
return response()->json([
'success' => false,
'message' => 'Something went wrong.',
], 500);
@ -586,11 +762,15 @@ public function update_env_by_uuid(Request $request)
public function create_bulk_envs(Request $request)
{
ray()->clearAll();
$teamId = get_team_id_from_token();
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalid_token();
return invalidTokenResponse();
}
$return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return;
}
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
@ -604,6 +784,7 @@ public function create_bulk_envs(Request $request)
$bulk_data = $request->get('data');
if (! $bulk_data) {
return response()->json([
'success' => false,
'message' => 'Bulk data is required.',
], 400);
}
@ -620,6 +801,7 @@ public function create_bulk_envs(Request $request)
]);
if ($validator->fails()) {
return response()->json([
'success' => false,
'message' => 'Validation failed.',
'errors' => $validator->errors(),
], 422);
@ -671,18 +853,18 @@ public function create_bulk_envs(Request $request)
}
return response()->json([
'message' => 'Environments updated.',
'success' => true,
'data' => serializeApiResponse($env),
]);
}
public function create_env(Request $request)
{
ray()->clearAll();
$allowedFields = ['key', 'value', 'is_preview', 'is_build_time', 'is_literal'];
$teamId = get_team_id_from_token();
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalid_token();
return invalidTokenResponse();
}
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
@ -710,6 +892,7 @@ public function create_env(Request $request)
}
return response()->json([
'success' => false,
'message' => 'Validation failed.',
'errors' => $errors,
], 422);
@ -719,6 +902,7 @@ public function create_env(Request $request)
$env = $application->environment_variables_preview->where('key', $request->key)->first();
if ($env) {
return response()->json([
'success' => false,
'message' => 'Environment variable already exists. Use PATCH request to update it.',
], 409);
} else {
@ -730,7 +914,10 @@ public function create_env(Request $request)
'is_literal' => $request->is_literal ?? false,
]);
return response()->json(serialize_api_response($env))->setStatusCode(201);
return response()->json([
'success' => true,
'data' => serializeApiResponse($env),
])->setStatusCode(201);
}
} else {
$env = $application->environment_variables->where('key', $request->key)->first();
@ -747,12 +934,16 @@ public function create_env(Request $request)
'is_literal' => $request->is_literal ?? false,
]);
return response()->json(serialize_api_response($env))->setStatusCode(201);
return response()->json([
'success' => true,
'data' => serializeApiResponse($env),
])->setStatusCode(201);
}
}
return response()->json([
'success' => false,
'message' => 'Something went wrong.',
], 500);
@ -760,10 +951,9 @@ public function create_env(Request $request)
public function delete_env_by_uuid(Request $request)
{
ray()->clearAll();
$teamId = get_team_id_from_token();
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalid_token();
return invalidTokenResponse();
}
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
@ -790,19 +980,19 @@ public function delete_env_by_uuid(Request $request)
public function action_deploy(Request $request)
{
$teamId = get_team_id_from_token();
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalid_token();
return invalidTokenResponse();
}
$force = $request->query->get('force') ?? false;
$instant_deploy = $request->query->get('instant_deploy') ?? false;
$uuid = $request->route('uuid');
if (! $uuid) {
return response()->json(['error' => 'UUID is required.'], 400);
return response()->json(['success' => false, 'message' => 'UUID is required.'], 400);
}
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
if (! $application) {
return response()->json(['error' => 'Application not found.'], 404);
return response()->json(['success' => false, 'message' => 'Application not found.'], 404);
}
$deployment_uuid = new Cuid2(7);
@ -817,9 +1007,12 @@ public function action_deploy(Request $request)
return response()->json(
[
'success' => true,
'message' => 'Deployment request queued.',
'deployment_uuid' => $deployment_uuid->toString(),
'deployment_api_url' => base_url().'/api/v1/deployment/'.$deployment_uuid->toString(),
'data' => [
'deployment_uuid' => $deployment_uuid->toString(),
'deployment_api_url' => base_url().'/api/v1/deployment/'.$deployment_uuid->toString(),
],
],
200
);
@ -827,43 +1020,53 @@ public function action_deploy(Request $request)
public function action_stop(Request $request)
{
$teamId = get_team_id_from_token();
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalid_token();
return invalidTokenResponse();
}
$uuid = $request->route('uuid');
$sync = $request->query->get('sync') ?? false;
if (! $uuid) {
return response()->json(['error' => 'UUID is required.'], 400);
return response()->json(['success' => false, 'message' => 'UUID is required.'], 400);
}
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
if (! $application) {
return response()->json(['error' => 'Application not found.'], 404);
return response()->json(['success' => false, 'message' => 'Application not found.'], 404);
}
if ($sync) {
StopApplication::run($application);
return response()->json(['message' => 'Stopped the application.'], 200);
return response()->json(
[
'success' => true,
'message' => 'Stopped the application.',
],
);
} else {
StopApplication::dispatch($application);
return response()->json(['message' => 'Stopping request queued.'], 200);
return response()->json(
[
'success' => true,
'message' => 'Stopping request queued.',
],
);
}
}
public function action_restart(Request $request)
{
$teamId = get_team_id_from_token();
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalid_token();
return invalidTokenResponse();
}
$uuid = $request->route('uuid');
if (! $uuid) {
return response()->json(['error' => 'UUID is required.'], 400);
return response()->json(['success' => false, 'message' => 'UUID is required.'], 400);
}
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
if (! $application) {
return response()->json(['error' => 'Application not found.'], 404);
return response()->json(['success' => false, 'message' => 'Application not found.'], 404);
}
$deployment_uuid = new Cuid2(7);
@ -877,31 +1080,25 @@ public function action_restart(Request $request)
return response()->json(
[
'success' => true,
'message' => 'Restart request queued.',
'deployment_uuid' => $deployment_uuid->toString(),
'deployment_api_url' => base_url().'/api/v1/deployment/'.$deployment_uuid->toString(),
'data' => [
'deployment_uuid' => $deployment_uuid->toString(),
'deployment_api_url' => base_url().'/api/v1/deployment/'.$deployment_uuid->toString(),
],
],
200
);
}
private function removeUnnecessaryFieldsFromRequest(Request $request)
{
$request->offsetUnset('project_uuid');
$request->offsetUnset('environment_name');
$request->offsetUnset('destination_uuid');
$request->offsetUnset('server_uuid');
$request->offsetUnset('type');
$request->offsetUnset('domains');
$request->offsetUnset('instant_deploy');
$request->offsetUnset('github_app_uuid');
$request->offsetUnset('private_key_uuid');
}
private function validateDataApplications(Request $request, Server $server)
{
$teamId = get_team_id_from_token();
$teamId = getTeamIdFromToken();
// Default build pack is nixpacks
if (! $request->has('build_pack')) {
$request->offsetSet('build_pack', 'nixpacks');
}
// Validate ports_mappings
if ($request->has('ports_mappings')) {
@ -910,6 +1107,7 @@ private function validateDataApplications(Request $request, Server $server)
$port = explode(':', $portMapping);
if (in_array($port[0], $ports)) {
return response()->json([
'success' => false,
'message' => 'Validation failed.',
'errors' => [
'ports_mappings' => 'The first number before : should be unique between mappings.',
@ -923,6 +1121,7 @@ private function validateDataApplications(Request $request, Server $server)
if ($request->has('custom_labels')) {
if (! isBase64Encoded($request->custom_labels)) {
return response()->json([
'success' => false,
'message' => 'Validation failed.',
'errors' => [
'custom_labels' => 'The custom_labels should be base64 encoded.',
@ -932,6 +1131,7 @@ private function validateDataApplications(Request $request, Server $server)
$customLabels = base64_decode($request->custom_labels);
if (mb_detect_encoding($customLabels, 'ASCII', true) === false) {
return response()->json([
'success' => false,
'message' => 'Validation failed.',
'errors' => [
'custom_labels' => 'The custom_labels should be base64 encoded.',
@ -954,12 +1154,14 @@ private function validateDataApplications(Request $request, Server $server)
});
if (count($errors) > 0) {
return response()->json([
'success' => false,
'message' => 'Validation failed.',
'errors' => $errors,
], 422);
}
if (checkIfDomainIsAlreadyUsed($fqdn, $teamId)) {
return response()->json([
'success' => false,
'message' => 'Validation failed.',
'errors' => [
'domains' => 'One of the domain is already used.',

View File

@ -0,0 +1,259 @@
<?php
namespace App\Http\Controllers\Api;
use App\Actions\Database\StartClickhouse;
use App\Actions\Database\StartDragonfly;
use App\Actions\Database\StartKeydb;
use App\Actions\Database\StartMariadb;
use App\Actions\Database\StartMongodb;
use App\Actions\Database\StartMysql;
use App\Actions\Database\StartPostgresql;
use App\Actions\Database\StartRedis;
use App\Actions\Database\StopDatabase;
use App\Enums\NewDatabaseTypes;
use App\Http\Controllers\Controller;
use App\Models\Project;
use App\Models\Server;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
class DatabasesController extends Controller
{
public function databases(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$projects = Project::where('team_id', $teamId)->get();
$databases = collect();
foreach ($projects as $project) {
$databases = $databases->merge($project->databases());
}
$databases = $databases->map(function ($database) {
return serializeApiResponse($database);
});
return response()->json([
'success' => true,
'data' => $databases,
]);
}
public function database_by_uuid(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
if (! $request->uuid) {
return response()->json(['success' => false, 'message' => 'UUID is required.'], 404);
}
$database = queryDatabaseByUuidWithinTeam($request->uuid, $teamId);
if (! $database) {
return response()->json(['success' => false, 'message' => 'Database not found.'], 404);
}
return response()->json([
'success' => true,
'data' => serializeApiResponse($database),
]);
}
public function create_database(Request $request)
{
$allowedFields = ['type', 'name', 'description', 'image', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'postgres_user', 'postgres_password', 'postgres_db', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares'];
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return;
}
$validator = customApiValidator($request->all(), [
'type' => ['required', Rule::enum(NewDatabaseTypes::class)],
'name' => 'string|max:255',
'description' => 'string|nullable',
'image' => 'string',
'project_uuid' => 'string|required',
'environment_name' => 'string|required',
'server_uuid' => 'string|required',
'destination_uuid' => 'string',
'postgres_user' => 'string',
'postgres_password' => 'string',
'postgres_db' => 'string',
'limits_memory' => 'string',
'limits_memory_swap' => 'string',
'limits_memory_swappiness' => 'numeric',
'limits_memory_reservation' => 'string',
'limits_cpus' => 'string',
'limits_cpuset' => 'string|nullable',
'limits_cpu_shares' => 'numeric',
'instant_deploy' => 'boolean',
]);
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
if ($validator->fails() || ! empty($extraFields)) {
$errors = $validator->errors();
if (! empty($extraFields)) {
foreach ($extraFields as $field) {
$errors->add($field, 'This field is not allowed.');
}
}
return response()->json([
'success' => false,
'message' => 'Validation failed.',
'errors' => $errors,
], 422);
}
$serverUuid = $request->server_uuid;
$instantDeploy = $request->instant_deploy ?? false;
$project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first();
if (! $project) {
return response()->json(['succes' => false, 'message' => 'Project not found.'], 404);
}
$environment = $project->environments()->where('name', $request->environment_name)->first();
if (! $environment) {
return response()->json(['success' => false, 'message' => 'Environment not found.'], 404);
}
$server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first();
if (! $server) {
return response()->json(['success' => false, 'message' => 'Server not found.'], 404);
}
$destinations = $server->destinations();
if ($destinations->count() == 0) {
return response()->json(['success' => false, 'message' => 'Server has no destinations.'], 400);
}
if ($destinations->count() > 1 && ! $request->has('destination_uuid')) {
return response()->json(['success' => false, 'message' => 'Server has multiple destinations and you do not set destination_uuid.'], 400);
}
$destination = $destinations->first();
if ($request->type === NewDatabaseTypes::POSTGRESQL->value) {
removeUnnecessaryFieldsFromRequest($request);
$database = create_standalone_postgresql($environment->id, $destination->uuid, $request->all());
if ($instantDeploy) {
StartPostgresql::dispatch($database);
}
return response()->json([
'success' => true,
'message' => 'Database starting queued.',
'data' => serializeApiResponse($database),
]);
} elseif ($request->type === NewDatabaseTypes::MARIADB->value) {
removeUnnecessaryFieldsFromRequest($request);
$database = create_standalone_mariadb($environment->id, $destination->uuid, $request->all());
if ($instantDeploy) {
StartMariadb::dispatch($database);
}
return response()->json([
'success' => true,
'message' => 'Database starting queued.',
'data' => serializeApiResponse($database),
]);
} elseif ($request->type === NewDatabaseTypes::MYSQL->value) {
removeUnnecessaryFieldsFromRequest($request);
$database = create_standalone_mysql($environment->id, $destination->uuid, $request->all());
if ($instantDeploy) {
StartMysql::dispatch($database);
}
return response()->json([
'success' => true,
'message' => 'Database starting queued.',
'data' => serializeApiResponse($database),
]);
} elseif ($request->type === NewDatabaseTypes::REDIS->value) {
removeUnnecessaryFieldsFromRequest($request);
$database = create_standalone_redis($environment->id, $destination->uuid, $request->all());
if ($instantDeploy) {
StartRedis::dispatch($database);
}
return response()->json([
'success' => true,
'message' => 'Database starting queued.',
'data' => serializeApiResponse($database),
]);
} elseif ($request->type === NewDatabaseTypes::DRAGONFLY->value) {
removeUnnecessaryFieldsFromRequest($request);
$database = create_standalone_dragonfly($environment->id, $destination->uuid, $request->all());
if ($instantDeploy) {
StartDragonfly::dispatch($database);
}
return response()->json([
'success' => true,
'message' => 'Database starting queued.',
'data' => serializeApiResponse($database),
]);
} elseif ($request->type === NewDatabaseTypes::KEYDB->value) {
removeUnnecessaryFieldsFromRequest($request);
$database = create_standalone_keydb($environment->id, $destination->uuid, $request->all());
if ($instantDeploy) {
StartKeydb::dispatch($database);
}
return response()->json([
'success' => true,
'message' => 'Database starting queued.',
'data' => serializeApiResponse($database),
]);
} elseif ($request->type === NewDatabaseTypes::CLICKHOUSE->value) {
removeUnnecessaryFieldsFromRequest($request);
$database = create_standalone_clickhouse($environment->id, $destination->uuid, $request->all());
if ($instantDeploy) {
StartClickhouse::dispatch($database);
}
return response()->json([
'success' => true,
'message' => 'Database starting queued.',
'data' => serializeApiResponse($database),
]);
} elseif ($request->type === NewDatabaseTypes::MONGODB->value) {
removeUnnecessaryFieldsFromRequest($request);
$database = create_standalone_mongodb($environment->id, $destination->uuid, $request->all());
if ($instantDeploy) {
StartMongodb::dispatch($database);
}
return response()->json([
'success' => true,
'message' => 'Database starting queued.',
'data' => serializeApiResponse($database),
]);
}
return response()->json(['success' => false, 'message' => 'Invalid database type requested.'], 400);
}
public function delete_by_uuid(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
if (! $request->uuid) {
return response()->json(['success' => false, 'message' => 'UUID is required.'], 404);
}
$database = queryDatabaseByUuidWithinTeam($request->uuid, $teamId);
if (! $database) {
return response()->json(['success' => false, 'message' => 'Database not found.'], 404);
}
StopDatabase::dispatch($database);
$database->delete();
return response()->json([
'success' => true,
'message' => 'Database deletion request queued.',
]);
}
}

View File

@ -18,13 +18,13 @@
use Illuminate\Http\Request;
use Visus\Cuid2\Cuid2;
class Deploy extends Controller
class DeployController extends Controller
{
public function deployments(Request $request)
{
$teamId = get_team_id_from_token();
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalid_token();
return invalidTokenResponse();
}
$servers = Server::whereTeamId($teamId)->get();
$deployments_per_server = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('server_id', $servers->pluck('id'))->get([
@ -38,39 +38,45 @@ public function deployments(Request $request)
'status',
])->sortBy('id')->toArray();
return response()->json(serialize_api_response($deployments_per_server), 200);
return response()->json([
'success' => true,
'data' => serializeApiResponse($deployments_per_server),
]);
}
public function deployment_by_uuid(Request $request)
{
$teamId = get_team_id_from_token();
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalid_token();
return invalidTokenResponse();
}
$uuid = $request->route('uuid');
if (! $uuid) {
return response()->json(['message' => 'UUID is required.'], 400);
return response()->json(['success' => false, 'message' => 'UUID is required.'], 400);
}
$deployment = ApplicationDeploymentQueue::where('deployment_uuid', $uuid)->first();
if (! $deployment) {
return response()->json(['message' => 'Deployment not found.'], 404);
return response()->json(['success' => false, 'message' => 'Deployment not found.'], 404);
}
return response()->json(serialize_api_response($deployment->makeHidden('logs')), 200);
return response()->json([
'success' => true,
'data' => serializeApiResponse($deployment->makeHidden('logs')),
]);
}
public function deploy(Request $request)
{
$teamId = get_team_id_from_token();
$teamId = getTeamIdFromToken();
$uuids = $request->query->get('uuid');
$tags = $request->query->get('tag');
$force = $request->query->get('force') ?? false;
if ($uuids && $tags) {
return response()->json(['message' => 'You can only use uuid or tag, not both.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
return response()->json(['success' => false, 'message' => 'You can only use uuid or tag, not both.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
}
if (is_null($teamId)) {
return invalid_token();
return invalidTokenResponse();
}
if ($tags) {
return $this->by_tags($tags, $teamId, $force);
@ -78,7 +84,7 @@ public function deploy(Request $request)
return $this->by_uuids($uuids, $teamId, $force);
}
return response()->json(['message' => 'You must provide uuid or tag.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
return response()->json(['success' => false, 'message' => 'You must provide uuid or tag.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
}
private function by_uuids(string $uuid, int $teamId, bool $force = false)
@ -87,7 +93,7 @@ private function by_uuids(string $uuid, int $teamId, bool $force = false)
$uuids = collect(array_filter($uuids));
if (count($uuids) === 0) {
return response()->json(['message' => 'No UUIDs provided.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
return response()->json(['success' => false, 'message' => 'No UUIDs provided.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
}
$deployments = collect();
$payload = collect();
@ -96,19 +102,22 @@ private function by_uuids(string $uuid, int $teamId, bool $force = false)
if ($resource) {
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
if ($deployment_uuid) {
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
$deployments->push(['success' => true, 'message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
} else {
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid]);
$deployments->push(['success' => true, 'message' => $return_message, 'resource_uuid' => $uuid]);
}
}
}
if ($deployments->count() > 0) {
$payload->put('deployments', $deployments->toArray());
return response()->json($payload->toArray(), 200);
return response()->json([
'success' => true,
'data' => serializeApiResponse($payload->toArray()),
]);
}
return response()->json(['message' => 'No resources found.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404);
return response()->json(['success' => false, 'message' => 'No resources found.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404);
}
public function by_tags(string $tags, int $team_id, bool $force = false)
@ -117,7 +126,7 @@ public function by_tags(string $tags, int $team_id, bool $force = false)
$tags = collect(array_filter($tags));
if (count($tags) === 0) {
return response()->json(['message' => 'No TAGs provided.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
return response()->json(['success' => false, 'message' => 'No TAGs provided.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
}
$message = collect([]);
$deployments = collect();
@ -153,10 +162,13 @@ public function by_tags(string $tags, int $team_id, bool $force = false)
$payload->put('details', $deployments->toArray());
}
return response()->json($payload->toArray(), 200);
return response()->json([
'success' => true,
'data' => serializeApiResponse($payload->toArray()),
]);
}
return response()->json(['message' => 'No resources found with this tag.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404);
return response()->json(['success' => false, 'message' => 'No resources found with this tag.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404);
}
public function deploy_resource($resource, bool $force = false): array
@ -164,7 +176,7 @@ public function deploy_resource($resource, bool $force = false): array
$message = null;
$deployment_uuid = null;
if (gettype($resource) !== 'object') {
return ['message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid];
return ['success' => false, 'message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid];
}
$type = $resource?->getMorphClass();
if ($type === 'App\Models\Application') {
@ -228,6 +240,6 @@ public function deploy_resource($resource, bool $force = false): array
$message = "Service {$resource->name} started. It could take a while, be patient.";
}
return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
return ['success' => true, 'message' => $message, 'deployment_uuid' => $deployment_uuid];
}
}

View File

@ -6,14 +6,13 @@
use App\Models\EnvironmentVariable;
use Illuminate\Http\Request;
class EnvironmentVariables extends Controller
class EnvironmentVariablesController extends Controller
{
public function delete_env_by_uuid(Request $request)
{
ray()->clearAll();
$teamId = get_team_id_from_token();
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalid_token();
return invalidTokenResponse();
}
$env = EnvironmentVariable::where('uuid', $request->env_uuid)->first();
if (! $env) {

View File

@ -1,44 +0,0 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Project as ModelsProject;
use Illuminate\Http\Request;
class Project extends Controller
{
public function projects(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$projects = ModelsProject::whereTeamId($teamId)->select('id', 'name', 'uuid')->get();
return response()->json($projects);
}
public function project_by_uuid(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']);
return response()->json($project);
}
public function environment_details(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
$environment = $project->environments()->whereName(request()->environment_name)->first()->load(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']);
return response()->json($environment);
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Project;
use Illuminate\Http\Request;
class ProjectController extends Controller
{
public function projects(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$projects = Project::whereTeamId($teamId)->select('id', 'name', 'uuid')->get();
return response()->json([
'success' => true,
'data' => serializeApiResponse($projects),
]);
}
public function project_by_uuid(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$project = Project::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']);
if (! $project) {
return response()->json(['success' => false, 'message' => 'Project not found.'], 404);
}
return response()->json([
'success' => true,
'data' => serializeApiResponse($project),
]);
}
public function environment_details(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$project = Project::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
$environment = $project->environments()->whereName(request()->environment_name)->first();
if (! $environment) {
return response()->json(['success' => false, 'message' => 'Environment not found.'], 404);
}
$environment = $environment->load(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']);
return response()->json([
'success' => true,
'data' => serializeApiResponse($environment),
]);
}
}

View File

@ -6,13 +6,13 @@
use App\Models\Project;
use Illuminate\Http\Request;
class Resources extends Controller
class ResourcesController extends Controller
{
public function resources(Request $request)
{
$teamId = get_team_id_from_token();
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalid_token();
return invalidTokenResponse();
}
$projects = Project::where('team_id', $teamId)->get();
$resources = collect();
@ -34,6 +34,9 @@ public function resources(Request $request)
return $payload;
});
return response()->json($resources);
return response()->json([
'success' => true,
'data' => serializeApiResponse($resources),
]);
}
}

View File

@ -0,0 +1,160 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\PrivateKey;
use Illuminate\Http\Request;
class SecurityController extends Controller
{
public function keys(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$keys = PrivateKey::where('team_id', $teamId)->get();
return response()->json([
'success' => true,
'data' => serializeApiResponse($keys),
]);
}
public function key_by_uuid(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$key = PrivateKey::where('team_id', $teamId)->where('uuid', $request->uuid)->first();
if (is_null($key)) {
return response()->json([
'success' => false,
'message' => 'Key not found.',
], 404);
}
return response()->json([
'success' => true,
'data' => serializeApiResponse($key),
]);
}
public function create_key(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return;
}
$validator = customApiValidator($request->all(), [
'name' => 'string|max:255',
'description' => 'string|max:255',
'private_key' => 'required|string',
]);
if ($validator->fails()) {
$errors = $validator->errors();
return response()->json([
'success' => false,
'message' => 'Validation failed.',
'errors' => $errors,
], 422);
}
if (! $request->name) {
$request->offsetSet('name', generate_random_name());
}
if (! $request->description) {
$request->offsetSet('description', 'Created by Coolify via API');
}
$key = PrivateKey::create([
'team_id' => $teamId,
'name' => $request->name,
'description' => $request->description,
'private_key' => $request->private_key,
]);
return response()->json([
'success' => true,
'data' => serializeApiResponse($key),
]);
}
public function update_key(Request $request)
{
$allowedFields = ['name', 'description', 'private_key'];
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return;
}
$validator = customApiValidator($request->all(), [
'name' => 'string|max:255',
'description' => 'string|max:255',
'private_key' => 'required|string',
]);
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
if ($validator->fails() || ! empty($extraFields)) {
$errors = $validator->errors();
if (! empty($extraFields)) {
foreach ($extraFields as $field) {
$errors->add($field, 'This field is not allowed.');
}
}
return response()->json([
'success' => false,
'message' => 'Validation failed.',
'errors' => $errors,
], 422);
}
$foundKey = PrivateKey::where('team_id', $teamId)->where('uuid', $request->uuid)->first();
if (is_null($foundKey)) {
return response()->json([
'success' => false,
'message' => 'Key not found.',
], 404);
}
$foundKey->update($request->all());
return response()->json([
'success' => true,
'data' => serializeApiResponse($foundKey),
])->setStatusCode(201);
}
public function delete_key(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
if (! $request->uuid) {
return response()->json(['success' => false, 'message' => 'UUID is required.'], 422);
}
$key = PrivateKey::where('team_id', $teamId)->where('uuid', $request->uuid)->first();
if (is_null($key)) {
return response()->json(['success' => false, 'message' => 'Key not found.'], 404);
}
$key->delete();
return response()->json([
'success' => true,
'message' => 'Key deleted.',
]);
}
}

View File

@ -8,14 +8,15 @@
use App\Models\Project;
use App\Models\Server as ModelsServer;
use Illuminate\Http\Request;
use Stringable;
class Servers extends Controller
class ServersController extends Controller
{
public function servers(Request $request)
{
$teamId = get_team_id_from_token();
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalid_token();
return invalidTokenResponse();
}
$servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port')->get()->load(['settings'])->map(function ($server) {
$server['is_reachable'] = $server->settings->is_reachable;
@ -23,16 +24,22 @@ public function servers(Request $request)
return $server;
});
$servers = $servers->map(function ($server) {
return serializeApiResponse($server);
});
return response()->json($servers);
return response()->json([
'success' => true,
'data' => $servers,
]);
}
public function server_by_uuid(Request $request)
{
$with_resources = $request->query('resources');
$teamId = get_team_id_from_token();
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalid_token();
return invalidTokenResponse();
}
$server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
if (is_null($server)) {
@ -60,22 +67,25 @@ public function server_by_uuid(Request $request)
$server->load(['settings']);
}
return response()->json($server);
return response()->json([
'success' => true,
'data' => serializeApiResponse($server),
]);
}
public function get_domains_by_server(Request $request)
{
$teamId = get_team_id_from_token();
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalid_token();
return invalidTokenResponse();
}
$uuid = $request->query->get('uuid');
$uuid = $request->get('uuid');
if ($uuid) {
$domains = Application::getDomainsByUuid($uuid);
return response()->json([
'uuid' => $uuid,
'domains' => $domains,
'success' => true,
'data' => serializeApiResponse($domains),
]);
}
$projects = Project::where('team_id', $teamId)->get();
@ -86,8 +96,13 @@ public function get_domains_by_server(Request $request)
foreach ($applications as $application) {
$ip = $application->destination->server->ip;
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
$f = str($fqdn)->replace('http://', '')->replace('https://', '')->explode('/');
return str(str($f[0])->explode(':')[0]);
})->filter(function (Stringable $fqdn) {
return $fqdn->isNotEmpty();
});
if ($ip === 'host.docker.internal') {
if ($settings->public_ipv4) {
$domains->push([
@ -122,7 +137,11 @@ public function get_domains_by_server(Request $request)
if ($service_applications->count() > 0) {
foreach ($service_applications as $application) {
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
$f = str($fqdn)->replace('http://', '')->replace('https://', '')->explode('/');
return str(str($f[0])->explode(':')[0]);
})->filter(function (Stringable $fqdn) {
return $fqdn->isNotEmpty();
});
if ($ip === 'host.docker.internal') {
if ($settings->public_ipv4) {
@ -162,6 +181,9 @@ public function get_domains_by_server(Request $request)
];
})->values();
return response()->json($domains);
return response()->json([
'success' => true,
'data' => serializeApiResponse($domains),
]);
}
}

View File

@ -1,74 +0,0 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class Team extends Controller
{
public function teams(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$teams = auth()->user()->teams;
return response()->json($teams);
}
public function team_by_id(Request $request)
{
$id = $request->id;
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$teams = auth()->user()->teams;
$team = $teams->where('id', $id)->first();
if (is_null($team)) {
return response()->json(['error' => 'Team not found.', 'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid'], 404);
}
return response()->json($team);
}
public function members_by_id(Request $request)
{
$id = $request->id;
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$teams = auth()->user()->teams;
$team = $teams->where('id', $id)->first();
if (is_null($team)) {
return response()->json(['error' => 'Team not found.', 'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid-members'], 404);
}
return response()->json($team->members);
}
public function current_team(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$team = auth()->user()->currentTeam();
return response()->json($team);
}
public function current_team_members(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$team = auth()->user()->currentTeam();
return response()->json($team->members);
}
}

View File

@ -0,0 +1,89 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class TeamController extends Controller
{
public function teams(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$teams = auth()->user()->teams;
return response()->json([
'success' => true,
'data' => serializeApiResponse($teams),
]);
}
public function team_by_id(Request $request)
{
$id = $request->id;
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$teams = auth()->user()->teams;
$team = $teams->where('id', $id)->first();
if (is_null($team)) {
return response()->json(['success' => false, 'message' => 'Team not found.', 'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid'], 404);
}
return response()->json([
'success' => true,
'data' => serializeApiResponse($team),
]);
}
public function members_by_id(Request $request)
{
$id = $request->id;
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$teams = auth()->user()->teams;
$team = $teams->where('id', $id)->first();
if (is_null($team)) {
return response()->json(['success' => false, 'message' => 'Team not found.', 'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid-members'], 404);
}
return response()->json([
'success' => true,
'data' => serializeApiResponse($team->members),
]);
}
public function current_team(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$team = auth()->user()->currentTeam();
return response()->json([
'success' => true,
'data' => serializeApiResponse($team),
]);
}
public function current_team_members(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$team = auth()->user()->currentTeam();
return response()->json([
'success' => true,
'data' => serializeApiResponse($team->members),
]);
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Http\Middleware;
use App\Models\InstanceSettings;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class ApiAllowed
{
public function handle(Request $request, Closure $next): Response
{
ray()->clearAll();
if (isCloud()) {
return $next($request);
}
$settings = InstanceSettings::get();
if ($settings->is_api_enabled === false) {
return response()->json(['success' => true, 'message' => 'API is disabled.'], 403);
}
if (! isDev()) {
if ($settings->allowed_ips) {
$allowedIps = explode(',', $settings->allowed_ips);
if (! in_array($request->ip(), $allowedIps)) {
return response()->json(['success' => true, 'message' => 'You are not allowed to access the API.'], 403);
}
}
}
return $next($request);
}
}

View File

@ -18,7 +18,8 @@ class Configuration extends Component
public bool $is_dns_validation_enabled;
// public bool $next_channel;
public bool $is_api_enabled;
protected string $dynamic_config_path = '/data/coolify/proxy/dynamic';
protected Server $server;
@ -30,6 +31,7 @@ class Configuration extends Component
'settings.public_port_max' => 'required',
'settings.custom_dns_servers' => 'nullable',
'settings.instance_name' => 'nullable',
'settings.allowed_ips' => 'nullable',
];
protected $validationAttributes = [
@ -38,6 +40,7 @@ class Configuration extends Component
'settings.public_port_min' => 'Public port min',
'settings.public_port_max' => 'Public port max',
'settings.custom_dns_servers' => 'Custom DNS servers',
'settings.allowed_ips' => 'Allowed IPs',
];
public function mount()
@ -45,8 +48,8 @@ public function mount()
$this->do_not_track = $this->settings->do_not_track;
$this->is_auto_update_enabled = $this->settings->is_auto_update_enabled;
$this->is_registration_enabled = $this->settings->is_registration_enabled;
// $this->next_channel = $this->settings->next_channel;
$this->is_dns_validation_enabled = $this->settings->is_dns_validation_enabled;
$this->is_api_enabled = $this->settings->is_api_enabled;
}
public function instantSave()
@ -55,12 +58,7 @@ public function instantSave()
$this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
$this->settings->is_registration_enabled = $this->is_registration_enabled;
$this->settings->is_dns_validation_enabled = $this->is_dns_validation_enabled;
// if ($this->next_channel) {
// $this->settings->next_channel = false;
// $this->next_channel = false;
// } else {
// $this->settings->next_channel = $this->next_channel;
// }
$this->settings->is_api_enabled = $this->is_api_enabled;
$this->settings->save();
$this->dispatch('success', 'Settings updated!');
}
@ -94,6 +92,13 @@ public function submit()
$this->settings->custom_dns_servers = $this->settings->custom_dns_servers->unique();
$this->settings->custom_dns_servers = $this->settings->custom_dns_servers->implode(',');
$this->settings->allowed_ips = str($this->settings->allowed_ips)->replaceEnd(',', '')->trim();
$this->settings->allowed_ips = str($this->settings->allowed_ips)->trim()->explode(',')->map(function ($ip) {
return str($ip)->trim();
});
$this->settings->allowed_ips = $this->settings->allowed_ips->unique();
$this->settings->allowed_ips = $this->settings->allowed_ips->implode(',');
$this->settings->save();
$this->server->setupDynamicProxyConfiguration();
if (! $error_show) {

View File

@ -17,6 +17,7 @@ class InstanceSettings extends Model implements SendsEmail
protected $casts = [
'resale_license' => 'encrypted',
'smtp_password' => 'encrypted',
'allowed_ip_ranges' => 'array',
];
public function fqdn(): Attribute

View File

@ -13,6 +13,8 @@ class StandaloneClickhouse extends BaseModel
protected $guarded = [];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
protected $casts = [
'clickhouse_password' => 'encrypted',
];
@ -178,17 +180,44 @@ public function team()
return data_get($this, 'environment.project.team');
}
public function databaseType(): Attribute
{
return new Attribute(
get: fn () => $this->type(),
);
}
public function type(): string
{
return 'standalone-clickhouse';
}
public function get_db_url(bool $useInternal = false): string
protected function internalDbUrl(): Attribute
{
return new Attribute(
get: fn () => "clickhouse://{$this->clickhouse_user}:{$this->clickhouse_password}@{$this->uuid}:9000/{$this->clickhouse_db}",
);
}
protected function externalDbUrl(): Attribute
{
return new Attribute(
get: function () {
if ($this->is_public && $this->public_port) {
return "clickhouse://{$this->clickhouse_user}:{$this->clickhouse_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->clickhouse_db}";
}
return null;
}
);
}
public function get_db_url(bool $useInternal = false)
{
if ($this->is_public && ! $useInternal) {
return "clickhouse://{$this->clickhouse_user}:{$this->clickhouse_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->clickhouse_db}";
return $this->externalDbUrl;
} else {
return "clickhouse://{$this->clickhouse_user}:{$this->clickhouse_password}@{$this->uuid}:9000/{$this->clickhouse_db}";
return $this->internalDbUrl;
}
}

View File

@ -13,6 +13,8 @@ class StandaloneDragonfly extends BaseModel
protected $guarded = [];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
protected $casts = [
'dragonfly_password' => 'encrypted',
];
@ -178,17 +180,44 @@ public function portsMappingsArray(): Attribute
);
}
public function databaseType(): Attribute
{
return new Attribute(
get: fn () => $this->type(),
);
}
public function type(): string
{
return 'standalone-dragonfly';
}
public function get_db_url(bool $useInternal = false): string
protected function internalDbUrl(): Attribute
{
return new Attribute(
get: fn () => "redis://:{$this->dragonfly_password}@{$this->uuid}:6379/0",
);
}
protected function externalDbUrl(): Attribute
{
return new Attribute(
get: function () {
if ($this->is_public && $this->public_port) {
return "redis://:{$this->dragonfly_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
}
return null;
}
);
}
public function get_db_url(bool $useInternal = false)
{
if ($this->is_public && ! $useInternal) {
return "redis://:{$this->dragonfly_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
return $this->externalDbUrl;
} else {
return "redis://:{$this->dragonfly_password}@{$this->uuid}:6379/0";
return $this->internalDbUrl;
}
}

View File

@ -13,6 +13,8 @@ class StandaloneKeydb extends BaseModel
protected $guarded = [];
protected $appends = ['internal_db_url', 'external_db_url'];
protected $casts = [
'keydb_password' => 'encrypted',
];
@ -178,17 +180,44 @@ public function portsMappingsArray(): Attribute
);
}
public function databaseType(): Attribute
{
return new Attribute(
get: fn () => $this->type(),
);
}
public function type(): string
{
return 'standalone-keydb';
}
public function get_db_url(bool $useInternal = false): string
protected function internalDbUrl(): Attribute
{
return new Attribute(
get: fn () => "redis://{$this->keydb_password}@{$this->uuid}:6379/0",
);
}
protected function externalDbUrl(): Attribute
{
return new Attribute(
get: function () {
if ($this->is_public && $this->public_port) {
return "redis://{$this->keydb_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
}
return null;
}
);
}
public function get_db_url(bool $useInternal = false)
{
if ($this->is_public && ! $useInternal) {
return "redis://{$this->keydb_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
return $this->externalDbUrl;
} else {
return "redis://{$this->keydb_password}@{$this->uuid}:6379/0";
return $this->internalDbUrl;
}
}

View File

@ -13,6 +13,8 @@ class StandaloneMariadb extends BaseModel
protected $guarded = [];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
protected $casts = [
'mariadb_password' => 'encrypted',
];
@ -161,6 +163,13 @@ public function isLogDrainEnabled()
return data_get($this, 'is_log_drain_enabled', false);
}
public function databaseType(): Attribute
{
return new Attribute(
get: fn () => $this->type(),
);
}
public function type(): string
{
return 'standalone-mariadb';
@ -183,12 +192,32 @@ public function portsMappingsArray(): Attribute
);
}
public function get_db_url(bool $useInternal = false): string
protected function internalDbUrl(): Attribute
{
return new Attribute(
get: fn () => "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->uuid}:3306/{$this->mariadb_database}",
);
}
protected function externalDbUrl(): Attribute
{
return new Attribute(
get: function () {
if ($this->is_public && $this->public_port) {
return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mariadb_database}";
}
return null;
}
);
}
public function get_db_url(bool $useInternal = false)
{
if ($this->is_public && ! $useInternal) {
return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mariadb_database}";
return $this->externalDbUrl;
} else {
return "mysql://{$this->mariadb_user}:{$this->mariadb_password}@{$this->uuid}:3306/{$this->mariadb_database}";
return $this->internalDbUrl;
}
}

View File

@ -13,6 +13,8 @@ class StandaloneMongodb extends BaseModel
protected $guarded = [];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
protected static function booted()
{
static::created(function ($database) {
@ -198,17 +200,44 @@ public function portsMappingsArray(): Attribute
);
}
public function databaseType(): Attribute
{
return new Attribute(
get: fn () => $this->type(),
);
}
public function type(): string
{
return 'standalone-mongodb';
}
protected function internalDbUrl(): Attribute
{
return new Attribute(
get: fn () => "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->uuid}:27017/?directConnection=true",
);
}
protected function externalDbUrl(): Attribute
{
return new Attribute(
get: function () {
if ($this->is_public && $this->public_port) {
return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->destination->server->getIp}:{$this->public_port}/?directConnection=true";
}
return null;
}
);
}
public function get_db_url(bool $useInternal = false)
{
if ($this->is_public && ! $useInternal) {
return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->destination->server->getIp}:{$this->public_port}/?directConnection=true";
return $this->externalDbUrl;
} else {
return "mongodb://{$this->mongo_initdb_root_username}:{$this->mongo_initdb_root_password}@{$this->uuid}:27017/?directConnection=true";
return $this->internalDbUrl;
}
}

View File

@ -13,6 +13,8 @@ class StandaloneMysql extends BaseModel
protected $guarded = [];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
protected $casts = [
'mysql_password' => 'encrypted',
'mysql_root_password' => 'encrypted',
@ -157,6 +159,13 @@ public function link()
return null;
}
public function databaseType(): Attribute
{
return new Attribute(
get: fn () => $this->type(),
);
}
public function type(): string
{
return 'standalone-mysql';
@ -184,12 +193,32 @@ public function portsMappingsArray(): Attribute
);
}
public function get_db_url(bool $useInternal = false): string
protected function internalDbUrl(): Attribute
{
return new Attribute(
get: fn () => "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->uuid}:3306/{$this->mysql_database}",
);
}
protected function externalDbUrl(): Attribute
{
return new Attribute(
get: function () {
if ($this->is_public && $this->public_port) {
return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mysql_database}";
}
return null;
}
);
}
public function get_db_url(bool $useInternal = false)
{
if ($this->is_public && ! $useInternal) {
return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->mysql_database}";
return $this->externalDbUrl;
} else {
return "mysql://{$this->mysql_user}:{$this->mysql_password}@{$this->uuid}:3306/{$this->mysql_database}";
return $this->internalDbUrl;
}
}

View File

@ -13,6 +13,8 @@ class StandalonePostgresql extends BaseModel
protected $guarded = [];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
protected $casts = [
'init_scripts' => 'array',
'postgres_password' => 'encrypted',
@ -179,17 +181,44 @@ public function team()
return data_get($this, 'environment.project.team');
}
public function databaseType(): Attribute
{
return new Attribute(
get: fn () => $this->type(),
);
}
public function type(): string
{
return 'standalone-postgresql';
}
public function get_db_url(bool $useInternal = false): string
protected function internalDbUrl(): Attribute
{
return new Attribute(
get: fn () => "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->uuid}:5432/{$this->postgres_db}",
);
}
protected function externalDbUrl(): Attribute
{
return new Attribute(
get: function () {
if ($this->is_public && $this->public_port) {
return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->postgres_db}";
}
return null;
}
);
}
public function get_db_url(bool $useInternal = false)
{
if ($this->is_public && ! $useInternal) {
return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->postgres_db}";
return $this->externalDbUrl;
} else {
return "postgres://{$this->postgres_user}:{$this->postgres_password}@{$this->uuid}:5432/{$this->postgres_db}";
return $this->internalDbUrl;
}
}

View File

@ -13,6 +13,8 @@ class StandaloneRedis extends BaseModel
protected $guarded = [];
protected $appends = ['internal_db_url', 'external_db_url', 'database_type'];
protected static function booted()
{
static::created(function ($database) {
@ -179,12 +181,39 @@ public function type(): string
return 'standalone-redis';
}
public function get_db_url(bool $useInternal = false): string
public function databaseType(): Attribute
{
return new Attribute(
get: fn () => $this->type(),
);
}
protected function internalDbUrl(): Attribute
{
return new Attribute(
get: fn () => "redis://:{$this->redis_password}@{$this->uuid}:6379/0",
);
}
protected function externalDbUrl(): Attribute
{
return new Attribute(
get: function () {
if ($this->is_public && $this->public_port) {
return "redis://:{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
}
return null;
}
);
}
public function get_db_url(bool $useInternal = false)
{
if ($this->is_public && ! $useInternal) {
return "redis://:{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
return $this->externalDbUrl;
} else {
return "redis://:{$this->redis_password}@{$this->uuid}:6379/0";
return $this->internalDbUrl;
}
}

View File

@ -3,25 +3,27 @@
use App\Enums\BuildPackTypes;
use App\Enums\RedirectTypes;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
function get_team_id_from_token()
function getTeamIdFromToken()
{
$token = auth()->user()->currentAccessToken();
return data_get($token, 'team_id');
}
function invalid_token()
function invalidTokenResponse()
{
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api-reference/authorization'], 400);
return response()->json(['success' => false, 'message' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api-reference/authorization'], 400);
}
function serialize_api_response($data)
function serializeApiResponse($data)
{
if (! $data instanceof Collection) {
$data = collect($data);
}
$data = $data->sortKeys();
$created_at = data_get($data, 'created_at');
$updated_at = data_get($data, 'updated_at');
if ($created_at) {
@ -33,6 +35,16 @@ function serialize_api_response($data)
unset($data['updated_at']);
$data['updated_at'] = $updated_at;
}
if (data_get($data, 'name')) {
$data = $data->prepend($data['name'], 'name');
}
if (data_get($data, 'description')) {
$data = $data->prepend($data['description'], 'description');
}
if (data_get($data, 'uuid')) {
$data = $data->prepend($data['uuid'], 'uuid');
}
if (data_get($data, 'id')) {
$data = $data->prepend($data['id'], 'id');
}
@ -90,3 +102,36 @@ function sharedDataApplications()
'manual_webhook_secret_gitea' => 'string|nullable',
];
}
function validateIncomingRequest(Request $request)
{
// check if request is json
if (! $request->isJson()) {
return response()->json([
'success' => false,
'message' => 'Invalid request.',
'error' => 'Content-Type must be application/json.',
], 400);
}
// check if request is valid json
if (! json_decode($request->getContent())) {
return response()->json([
'success' => false,
'message' => 'Invalid request.',
'error' => 'Invalid JSON.',
], 400);
}
}
function removeUnnecessaryFieldsFromRequest(Request $request)
{
$request->offsetUnset('project_uuid');
$request->offsetUnset('environment_name');
$request->offsetUnset('destination_uuid');
$request->offsetUnset('server_uuid');
$request->offsetUnset('type');
$request->offsetUnset('domains');
$request->offsetUnset('instant_deploy');
$request->offsetUnset('github_app_uuid');
$request->offsetUnset('private_key_uuid');
}

View File

@ -19,131 +19,163 @@ function generate_database_name(string $type): string
return $type.'-database-'.$cuid;
}
function create_standalone_postgresql($environment_id, $destination_uuid): StandalonePostgresql
function create_standalone_postgresql($environmentId, $destinationUuid, ?array $otherData = null): StandalonePostgresql
{
// TODO: If another type of destination is added, this will need to be updated.
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
$destination = StandaloneDocker::where('uuid', $destinationUuid)->first();
if (! $destination) {
throw new Exception('Destination not found');
}
$database = new StandalonePostgresql();
$database->name = generate_database_name('postgresql');
$database->postgres_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
$database->environment_id = $environmentId;
$database->destination_id = $destination->id;
$database->destination_type = $destination->getMorphClass();
if ($otherData) {
$database->fill($otherData);
}
$database->save();
return StandalonePostgresql::create([
'name' => generate_database_name('postgresql'),
'postgres_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
'environment_id' => $environment_id,
'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(),
]);
return $database;
}
function create_standalone_redis($environment_id, $destination_uuid): StandaloneRedis
function create_standalone_redis($environment_id, $destination_uuid, ?array $otherData = null): StandaloneRedis
{
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (! $destination) {
throw new Exception('Destination not found');
}
$database = new StandaloneRedis();
$database->name = generate_database_name('redis');
$database->redis_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
$database->environment_id = $environment_id;
$database->destination_id = $destination->id;
$database->destination_type = $destination->getMorphClass();
if ($otherData) {
$database->fill($otherData);
}
$database->save();
return StandaloneRedis::create([
'name' => generate_database_name('redis'),
'redis_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
'environment_id' => $environment_id,
'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(),
]);
return $database;
}
function create_standalone_mongodb($environment_id, $destination_uuid): StandaloneMongodb
function create_standalone_mongodb($environment_id, $destination_uuid, ?array $otherData = null): StandaloneMongodb
{
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (! $destination) {
throw new Exception('Destination not found');
}
$database = new StandaloneMongodb();
$database->name = generate_database_name('mongodb');
$database->mongo_initdb_root_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
$database->environment_id = $environment_id;
$database->destination_id = $destination->id;
$database->destination_type = $destination->getMorphClass();
if ($otherData) {
$database->fill($otherData);
}
$database->save();
return StandaloneMongodb::create([
'name' => generate_database_name('mongodb'),
'mongo_initdb_root_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
'environment_id' => $environment_id,
'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(),
]);
return $database;
}
function create_standalone_mysql($environment_id, $destination_uuid): StandaloneMysql
function create_standalone_mysql($environment_id, $destination_uuid, ?array $otherData = null): StandaloneMysql
{
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (! $destination) {
throw new Exception('Destination not found');
}
$database = new StandaloneMysql();
$database->name = generate_database_name('mysql');
$database->mysql_root_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
$database->mysql_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
$database->environment_id = $environment_id;
$database->destination_id = $destination->id;
$database->destination_type = $destination->getMorphClass();
if ($otherData) {
$database->fill($otherData);
}
$database->save();
return StandaloneMysql::create([
'name' => generate_database_name('mysql'),
'mysql_root_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
'mysql_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
'environment_id' => $environment_id,
'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(),
]);
return $database;
}
function create_standalone_mariadb($environment_id, $destination_uuid): StandaloneMariadb
function create_standalone_mariadb($environment_id, $destination_uuid, ?array $otherData = null): StandaloneMariadb
{
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (! $destination) {
throw new Exception('Destination not found');
}
$database = new StandaloneMariadb();
$database->name = generate_database_name('mariadb');
$database->mariadb_root_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
$database->mariadb_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
$database->environment_id = $environment_id;
$database->destination_id = $destination->id;
$database->destination_type = $destination->getMorphClass();
return StandaloneMariadb::create([
'name' => generate_database_name('mariadb'),
'mariadb_root_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
'mariadb_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
'environment_id' => $environment_id,
'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(),
]);
if ($otherData) {
$database->fill($otherData);
}
$database->save();
return $database;
}
function create_standalone_keydb($environment_id, $destination_uuid): StandaloneKeydb
function create_standalone_keydb($environment_id, $destination_uuid, ?array $otherData = null): StandaloneKeydb
{
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (! $destination) {
throw new Exception('Destination not found');
}
$database = new StandaloneKeydb();
$database->name = generate_database_name('keydb');
$database->keydb_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
$database->environment_id = $environment_id;
$database->destination_id = $destination->id;
$database->destination_type = $destination->getMorphClass();
if ($otherData) {
$database->fill($otherData);
}
$database->save();
return StandaloneKeydb::create([
'name' => generate_database_name('keydb'),
'keydb_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
'environment_id' => $environment_id,
'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(),
]);
return $database;
}
function create_standalone_dragonfly($environment_id, $destination_uuid): StandaloneDragonfly
function create_standalone_dragonfly($environment_id, $destination_uuid, ?array $otherData = null): StandaloneDragonfly
{
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (! $destination) {
throw new Exception('Destination not found');
}
$database = new StandaloneDragonfly();
$database->name = generate_database_name('dragonfly');
$database->dragonfly_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
$database->environment_id = $environment_id;
$database->destination_id = $destination->id;
$database->destination_type = $destination->getMorphClass();
if ($otherData) {
$database->fill($otherData);
}
$database->save();
return StandaloneDragonfly::create([
'name' => generate_database_name('dragonfly'),
'dragonfly_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
'environment_id' => $environment_id,
'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(),
]);
return $database;
}
function create_standalone_clickhouse($environment_id, $destination_uuid): StandaloneClickhouse
function create_standalone_clickhouse($environment_id, $destination_uuid, ?array $otherData = null): StandaloneClickhouse
{
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (! $destination) {
throw new Exception('Destination not found');
}
$database = new StandaloneClickhouse();
$database->name = generate_database_name('clickhouse');
$database->clickhouse_admin_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
$database->environment_id = $environment_id;
$database->destination_id = $destination->id;
$database->destination_type = $destination->getMorphClass();
if ($otherData) {
$database->fill($otherData);
}
$database->save();
return StandaloneClickhouse::create([
'name' => generate_database_name('clickhouse'),
'clickhouse_admin_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
'environment_id' => $environment_id,
'destination_id' => $destination->id,
'destination_type' => $destination->getMorphClass(),
]);
return $database;
}
/**

View File

@ -538,6 +538,43 @@ function getResourceByUuid(string $uuid, ?int $teamId = null)
return null;
}
function queryDatabaseByUuidWithinTeam(string $uuid, string $teamId)
{
$postgresql = StandalonePostgresql::whereUuid($uuid)->first();
if ($postgresql && $postgresql->team()->id == $teamId) {
return $postgresql->unsetRelation('environment')->unsetRelation('destination');
}
$redis = StandaloneRedis::whereUuid($uuid)->first();
if ($redis && $redis->team()->id == $teamId) {
return $redis->unsetRelation('environment');
}
$mongodb = StandaloneMongodb::whereUuid($uuid)->first();
if ($mongodb && $mongodb->team()->id == $teamId) {
return $mongodb->unsetRelation('environment');
}
$mysql = StandaloneMysql::whereUuid($uuid)->first();
if ($mysql && $mysql->team()->id == $teamId) {
return $mysql->unsetRelation('environment');
}
$mariadb = StandaloneMariadb::whereUuid($uuid)->first();
if ($mariadb && $mariadb->team()->id == $teamId) {
return $mariadb->unsetRelation('environment');
}
$keydb = StandaloneKeydb::whereUuid($uuid)->first();
if ($keydb && $keydb->team()->id == $teamId) {
return $keydb->unsetRelation('environment');
}
$dragonfly = StandaloneDragonfly::whereUuid($uuid)->first();
if ($dragonfly && $dragonfly->team()->id == $teamId) {
return $dragonfly->unsetRelation('environment');
}
$clickhouse = StandaloneClickhouse::whereUuid($uuid)->first();
if ($clickhouse && $clickhouse->team()->id == $teamId) {
return $clickhouse->unsetRelation('environment');
}
return null;
}
function queryResourcesByUuid(string $uuid)
{
$resource = null;

View File

@ -0,0 +1,24 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('instance_settings', function (Blueprint $table) {
$table->boolean('is_api_enabled')->default(true);
$table->text('allowed_ips')->nullable();
});
}
public function down(): void
{
Schema::table('instance_settings', function (Blueprint $table) {
$table->dropColumn('is_api_enabled');
$table->dropColumn('allowed_ips');
});
}
};

View File

@ -25,7 +25,16 @@
<x-forms.input type="number" id="settings.public_port_max" label="Public Port Max" />
</div> --}}
</div>
<h2 class="pt-6">API</h2>
<div class="md:w-96">
<x-forms.checkbox instantSave id="is_api_enabled" label="Enabled" />
</div>
<x-forms.input id="settings.allowed_ips" label="Allowed IPs"
helper="Allowed IP lists for the API. A comma separated list of IPs. Empty means you allow from everywhere."
placeholder="1.1.1.1,8.8.8.8" />
</form>
<h2 class="pt-6">Advanced</h2>
<div class="text-right md:w-96">
@if (!is_null(env('AUTOUPDATE', null)))
@ -36,13 +45,5 @@
@endif
<x-forms.checkbox instantSave id="is_registration_enabled" label="Registration Allowed" />
<x-forms.checkbox instantSave id="do_not_track" label="Do Not Track" />
{{-- @if ($next_channel)
<x-forms.checkbox instantSave helper="Not recommended. Only if you like to live on the edge."
id="next_channel" label="Enable pre-release (early) updates" />
@else
<x-forms.checkbox disabled instantSave
helper="Currently disabled. Not recommended. Only if you like to live on the edge." id="next_channel"
label="Enable pre-release (early) updates" />
@endif --}}
</div>
</div>

View File

@ -1,11 +1,16 @@
<?php
use App\Http\Controllers\Api\Applications;
use App\Http\Controllers\Api\Deploy;
use App\Http\Controllers\Api\EnvironmentVariables;
use App\Http\Controllers\Api\Resources;
use App\Http\Controllers\Api\Servers;
use App\Http\Controllers\Api\Team;
use App\Http\Controllers\Api\ApplicationsController;
use App\Http\Controllers\Api\DatabasesController;
use App\Http\Controllers\Api\DeployController;
use App\Http\Controllers\Api\EnvironmentVariablesController;
use App\Http\Controllers\Api\ProjectController;
use App\Http\Controllers\Api\ResourcesController;
use App\Http\Controllers\Api\SecurityController;
use App\Http\Controllers\Api\ServersController;
use App\Http\Controllers\Api\TeamController;
use App\Http\Middleware\ApiAllowed;
use App\Models\InstanceSettings;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Route;
@ -22,59 +27,106 @@
]);
}
return response()->json(['message' => 'Feedback sent.'], 200);
return response()->json(['success' => true, 'message' => 'Feedback sent.'], 200);
});
Route::group([
'middleware' => ['auth:sanctum'],
'prefix' => 'v1',
], function () {
Route::get('/enable', function () {
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
if ($teamId !== '0') {
return response()->json(['success' => false, 'message' => 'You are not allowed to enable the API.'], 403);
}
$settings = InstanceSettings::get();
$settings->update(['is_api_enabled' => true]);
return response()->json(['success' => true, 'message' => 'API enabled.'], 200);
});
Route::get('/disable', function () {
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
if ($teamId !== '0') {
return response()->json(['success' => false, 'message' => 'You are not allowed to disable the API.'], 403);
}
$settings = InstanceSettings::get();
$settings->update(['is_api_enabled' => false]);
return response()->json(['success' => true, 'message' => 'API disabled.'], 200);
});
});
Route::group([
'middleware' => ['auth:sanctum', ApiAllowed::class],
'prefix' => 'v1',
], function () {
Route::get('/version', function () {
return response(config('version'));
});
Route::match(['get', 'post'], '/deploy', [Deploy::class, 'deploy']);
Route::get('/deployments', [Deploy::class, 'deployments']);
Route::get('/deployments/{uuid}', [Deploy::class, 'deployment_by_uuid']);
// Add environments endpoints
Route::get('/servers', [Servers::class, 'servers']);
Route::get('/servers/{uuid}', [Servers::class, 'server_by_uuid']);
Route::get('/servers/domains', [Servers::class, 'get_domains_by_server']);
Route::get('/teams', [TeamController::class, 'teams']);
Route::get('/teams/current', [TeamController::class, 'current_team']);
Route::get('/teams/current/members', [TeamController::class, 'current_team_members']);
Route::get('/teams/{id}', [TeamController::class, 'team_by_id']);
Route::get('/teams/{id}/members', [TeamController::class, 'members_by_id']);
Route::get('/resources', [Resources::class, 'resources']);
Route::get('/projects', [ProjectController::class, 'projects']);
Route::get('/projects/{uuid}', [ProjectController::class, 'project_by_uuid']);
Route::get('/projects/{uuid}/{environment_name}', [ProjectController::class, 'environment_details']);
Route::get('/applications', [Applications::class, 'applications']);
Route::post('/applications', [Applications::class, 'create_application']);
Route::get('/security/keys', [SecurityController::class, 'keys']);
Route::post('/security/keys', [SecurityController::class, 'create_key']);
Route::get('/applications/{uuid}', [Applications::class, 'application_by_uuid']);
Route::patch('/applications/{uuid}', [Applications::class, 'update_by_uuid']);
Route::delete('/applications/{uuid}', [Applications::class, 'delete_by_uuid']);
Route::get('/security/keys/{uuid}', [SecurityController::class, 'key_by_uuid']);
Route::patch('/security/keys/{uuid}', [SecurityController::class, 'update_key']);
Route::delete('/security/keys/{uuid}', [SecurityController::class, 'delete_key']);
Route::get('/applications/{uuid}/envs', [Applications::class, 'envs_by_uuid']);
Route::post('/applications/{uuid}/envs', [Applications::class, 'create_env']);
Route::post('/applications/{uuid}/envs/bulk', [Applications::class, 'create_bulk_envs']);
Route::patch('/applications/{uuid}/envs', [Applications::class, 'update_env_by_uuid']);
Route::delete('/applications/{uuid}/envs/{env_uuid}', [Applications::class, 'delete_env_by_uuid']);
Route::match(['get', 'post'], '/deploy', [DeployController::class, 'deploy']);
Route::match(['get', 'post'], '/applications/{uuid}/action/deploy', [Applications::class, 'action_deploy']);
Route::match(['get', 'post'], '/applications/{uuid}/action/restart', [Applications::class, 'action_restart']);
Route::match(['get', 'post'], '/applications/{uuid}/action/stop', [Applications::class, 'action_stop']);
Route::get('/deployments', [DeployController::class, 'deployments']);
Route::get('/deployments/{uuid}', [DeployController::class, 'deployment_by_uuid']);
Route::delete('/envs/{env_uuid}', [EnvironmentVariables::class, 'delete_env_by_uuid']);
Route::get('/servers', [ServersController::class, 'servers']);
Route::get('/servers/{uuid}', [ServersController::class, 'server_by_uuid']);
Route::get('/servers/{uuid}/domains', [ServersController::class, 'get_domains_by_server']);
Route::get('/teams', [Team::class, 'teams']);
Route::get('/teams/current', [Team::class, 'current_team']);
Route::get('/teams/current/members', [Team::class, 'current_team_members']);
Route::get('/teams/{id}', [Team::class, 'team_by_id']);
Route::get('/teams/{id}/members', [Team::class, 'members_by_id']);
Route::get('/resources', [ResourcesController::class, 'resources']);
Route::get('/applications', [ApplicationsController::class, 'applications']);
Route::post('/applications', [ApplicationsController::class, 'create_application']);
Route::get('/applications/{uuid}', [ApplicationsController::class, 'application_by_uuid']);
Route::patch('/applications/{uuid}', [ApplicationsController::class, 'update_by_uuid']);
Route::delete('/applications/{uuid}', [ApplicationsController::class, 'delete_by_uuid']);
Route::get('/applications/{uuid}/envs', [ApplicationsController::class, 'envs_by_uuid']);
Route::post('/applications/{uuid}/envs', [ApplicationsController::class, 'create_env']);
Route::post('/applications/{uuid}/envs/bulk', [ApplicationsController::class, 'create_bulk_envs']);
Route::patch('/applications/{uuid}/envs', [ApplicationsController::class, 'update_env_by_uuid']);
Route::delete('/applications/{uuid}/envs/{env_uuid}', [ApplicationsController::class, 'delete_env_by_uuid']);
Route::match(['get', 'post'], '/applications/{uuid}/action/deploy', [ApplicationsController::class, 'action_deploy']);
Route::match(['get', 'post'], '/applications/{uuid}/action/restart', [ApplicationsController::class, 'action_restart']);
Route::match(['get', 'post'], '/applications/{uuid}/action/stop', [ApplicationsController::class, 'action_stop']);
Route::get('/databases', [DatabasesController::class, 'databases']);
Route::post('/databases', [DatabasesController::class, 'create_database']);
Route::get('/databases/{uuid}', [DatabasesController::class, 'database_by_uuid']);
// Route::patch('/databases/{uuid}', [DatabasesController::class, 'update_by_uuid']);
Route::delete('/databases/{uuid}', [DatabasesController::class, 'delete_by_uuid']);
Route::delete('/envs/{env_uuid}', [EnvironmentVariablesController::class, 'delete_env_by_uuid']);
// Route::get('/projects', [Project::class, 'projects']);
//Route::get('/project/{uuid}', [Project::class, 'project_by_uuid']);
//Route::get('/project/{uuid}/{environment_name}', [Project::class, 'environment_details']);
});
Route::any('/{any}', function () {
return response()->json(['error' => 'Not found.'], 404);
return response()->json(['success' => false, 'message' => 'Not found.', 'docs' => 'https://coolify.io/docs'], 404);
})->where('any', '.*');
// Route::middleware(['throttle:5'])->group(function () {