Files
fotospiel-app/app/Services/Dokploy/DokployClient.php
Codex Agent 87f348462b
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled
Load Dokploy project details for compose data
2026-01-29 11:00:53 +01:00

388 lines
11 KiB
PHP

<?php
namespace App\Services\Dokploy;
use App\Models\InfrastructureActionLog;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Http\Client\Factory as HttpFactory;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
class DokployClient
{
public function __construct(private readonly HttpFactory $http) {}
public function applicationStatus(string $applicationId): array
{
return $this->cached($this->applicationCacheKey($applicationId), function () use ($applicationId) {
$application = $this->get('/application.one', [
'applicationId' => $applicationId,
]);
$appName = Arr::get($application, 'appName') ?? Arr::get($application, 'name');
$monitoring = [];
if ($appName) {
$monitoring = $this->optionalGet('/application.readAppMonitoring', [
'appName' => $appName,
]);
}
return [
'application' => $application,
'monitoring' => $monitoring,
'appName' => $appName,
];
}, 30);
}
public function projects(): array
{
return $this->cached($this->projectsCacheKey(), function () {
$projects = $this->get('/project.all');
return is_array($projects) ? $projects : [];
}, 60);
}
public function project(string $projectId): array
{
return $this->cached($this->projectCacheKey($projectId), function () use ($projectId) {
$project = $this->get('/project.one', [
'projectId' => $projectId,
]);
return is_array($project) ? $project : [];
}, 60);
}
public function findProject(string $projectIdOrName): ?array
{
$projects = $this->projects();
foreach ($projects as $project) {
if (Arr::get($project, 'projectId') === $projectIdOrName) {
return $project;
}
}
foreach ($projects as $project) {
if (
Arr::get($project, 'name') === $projectIdOrName
|| Arr::get($project, 'projectName') === $projectIdOrName
) {
return $project;
}
}
return null;
}
public function recentDeployments(string $applicationId, int $limit = 5): array
{
return $this->cached($this->deploymentCacheKey($applicationId), function () use ($applicationId, $limit) {
$deployments = $this->get('/deployment.all', [
'applicationId' => $applicationId,
]);
if (! is_array($deployments)) {
return [];
}
return array_slice($deployments, 0, $limit);
}, 60);
}
public function reloadApplication(string $applicationId, ?Authenticatable $actor = null): array
{
$status = $this->applicationStatus($applicationId);
$appName = Arr::get($status, 'appName');
if (! $appName) {
throw new \RuntimeException('Dokploy application name is required to reload the service.');
}
$response = $this->dispatchAction(
$applicationId,
'reload',
[
'applicationId' => $applicationId,
'appName' => $appName,
],
fn (array $payload) => $this->post('/application.reload', $payload),
$actor,
);
$this->forgetApplicationCaches($applicationId);
return $response;
}
public function redeployApplication(string $applicationId, ?Authenticatable $actor = null): array
{
$response = $this->dispatchAction(
$applicationId,
'redeploy',
[
'applicationId' => $applicationId,
],
fn (array $payload) => $this->post('/application.redeploy', $payload),
$actor,
);
$this->forgetApplicationCaches($applicationId);
return $response;
}
public function composeStatus(string $composeId): array
{
return $this->cached($this->composeCacheKey($composeId), function () use ($composeId) {
$compose = $this->get('/compose.one', [
'composeId' => $composeId,
]);
$services = $this->optionalGet('/compose.loadServices', [
'composeId' => $composeId,
'type' => 'cache',
]);
return [
'compose' => $compose,
'services' => $services,
];
}, 30);
}
public function composeDeployments(string $composeId, int $limit = 5): array
{
return $this->cached($this->composeDeploymentsCacheKey($composeId), function () use ($composeId, $limit) {
$deployments = $this->get('/deployment.allByCompose', [
'composeId' => $composeId,
]);
if (! is_array($deployments)) {
return [];
}
return array_slice($deployments, 0, $limit);
}, 60);
}
public function redeployCompose(string $composeId, ?Authenticatable $actor = null): array
{
$response = $this->dispatchAction(
$composeId,
'compose.redeploy',
[
'composeId' => $composeId,
],
fn (array $payload) => $this->post('/compose.redeploy', $payload),
$actor,
);
$this->forgetComposeCaches($composeId);
return $response;
}
public function deployCompose(string $composeId, ?Authenticatable $actor = null): array
{
$response = $this->dispatchAction(
$composeId,
'compose.deploy',
[
'composeId' => $composeId,
],
fn (array $payload) => $this->post('/compose.deploy', $payload),
$actor,
);
$this->forgetComposeCaches($composeId);
return $response;
}
public function stopCompose(string $composeId, ?Authenticatable $actor = null): array
{
$response = $this->dispatchAction(
$composeId,
'compose.stop',
[
'composeId' => $composeId,
],
fn (array $payload) => $this->post('/compose.stop', $payload),
$actor,
);
$this->forgetComposeCaches($composeId);
return $response;
}
protected function cached(string $key, callable $callback, int $seconds): mixed
{
return Cache::remember($key, now()->addSeconds($seconds), $callback);
}
protected function optionalGet(string $path, array $query = []): array
{
try {
return $this->get($path, $query);
} catch (\Throwable $exception) {
Log::warning('[Dokploy] Optional GET failed', [
'path' => $path,
'message' => $exception->getMessage(),
]);
return [];
}
}
protected function get(string $path, array $query = []): array
{
$response = $this->request()->get($this->normalizePath($path), $query);
if ($response->failed()) {
$this->logFailure('GET', $path, $response);
throw new RequestException($response);
}
return $response->json() ?? [];
}
protected function post(string $path, array $payload = []): Response
{
$response = $this->request()->post($this->normalizePath($path), $payload);
if ($response->failed()) {
$this->logFailure('POST', $path, $response);
throw new RequestException($response);
}
return $response;
}
protected function request(): PendingRequest
{
$baseUrl = config('dokploy.api.base_url');
$token = config('dokploy.api.token');
$timeout = config('dokploy.api.timeout', 10);
if (! $baseUrl || ! $token) {
throw new \RuntimeException('Dokploy API is not configured.');
}
return $this->http
->baseUrl($baseUrl)
->timeout($timeout)
->acceptJson()
->withHeaders([
'x-api-key' => $token,
]);
}
protected function normalizePath(string $path): string
{
return '/'.ltrim($path, '/');
}
protected function logFailure(string $method, string $path, Response $response): void
{
Log::error('[Dokploy] API request failed', [
'method' => $method,
'path' => $path,
'status' => $response->status(),
'body' => $response->body(),
]);
}
protected function dispatchAction(
string $targetId,
string $action,
array $payload,
callable $callback,
?Authenticatable $actor = null,
): array {
try {
$response = $callback($payload);
$body = $response->json() ?? [];
$status = $response->status();
} catch (\Throwable $exception) {
$this->logAction($targetId, $action, $payload, [
'error' => $exception->getMessage(),
], null, $actor);
throw $exception;
}
$this->logAction($targetId, $action, $payload, $body, $status, $actor);
return $body;
}
protected function logAction(
string $targetId,
string $action,
array $payload,
array $response,
?int $status,
?Authenticatable $actor = null,
): void {
InfrastructureActionLog::create([
'user_id' => $actor?->getAuthIdentifier() ?? auth()->id(),
'service_id' => $targetId,
'action' => $action,
'payload' => $payload,
'response' => $response,
'status_code' => $status,
]);
}
protected function applicationCacheKey(string $applicationId): string
{
return "dokploy.application.{$applicationId}";
}
protected function deploymentCacheKey(string $applicationId): string
{
return "dokploy.deployments.{$applicationId}";
}
protected function composeCacheKey(string $composeId): string
{
return "dokploy.compose.{$composeId}";
}
protected function composeDeploymentsCacheKey(string $composeId): string
{
return "dokploy.compose.deployments.{$composeId}";
}
protected function projectsCacheKey(): string
{
return 'dokploy.projects';
}
protected function projectCacheKey(string $projectId): string
{
return "dokploy.project.{$projectId}";
}
protected function forgetApplicationCaches(string $applicationId): void
{
Cache::forget($this->applicationCacheKey($applicationId));
Cache::forget($this->deploymentCacheKey($applicationId));
}
protected function forgetComposeCaches(string $composeId): void
{
Cache::forget($this->composeCacheKey($composeId));
Cache::forget($this->composeDeploymentsCacheKey($composeId));
}
}