Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions src/Data/CreateDaemonData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace PhpDevKits\ForgeSdk\Data;

use PhpDevKits\ForgeSdk\Enums\DaemonUser;

/**
* Input payload for POST /orgs/{org}/servers/{server}/background-processes.
*/
final readonly class CreateDaemonData
{
public function __construct(
public string $name,
public string $command,
public DaemonUser $user,
public int $processes = 1,
public ?int $siteId = null,
public ?string $directory = null,
public ?int $startSecs = null,
public ?int $stopWaitSecs = null,
public ?string $stopSignal = null,
) {}

/**
* @return array<string, mixed>
*/
public function toArray(): array
{
$payload = [
'name' => $this->name,
'command' => $this->command,
'user' => $this->user->value,
'processes' => $this->processes,
];

if ($this->siteId !== null) {
$payload['site_id'] = $this->siteId;
}

if ($this->directory !== null) {
$payload['directory'] = $this->directory;
}

if ($this->startSecs !== null) {
$payload['startsecs'] = $this->startSecs;
}

if ($this->stopWaitSecs !== null) {
$payload['stopwaitsecs'] = $this->stopWaitSecs;
}

if ($this->stopSignal !== null) {
$payload['stopsignal'] = $this->stopSignal;
}

return $payload;
}
}
119 changes: 119 additions & 0 deletions src/Data/Daemon.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php

declare(strict_types=1);

namespace PhpDevKits\ForgeSdk\Data;

use DateTimeImmutable;
use InvalidArgumentException;
use JsonSerializable;
use Override;
use Throwable;

final readonly class Daemon implements JsonSerializable
{
public function __construct(
public string $id,
public string $command,
public string $user,
public ?string $directory,
public int $processes,
public string $status,
public DateTimeImmutable $createdAt,
) {}

/**
* @param array<array-key, mixed> $data A JSON:API `BackgroundProcessResource` object.
*/
public static function from(array $data): self
{
$id = $data['id'] ?? null;
if (! is_string($id)) {
throw new InvalidArgumentException('Daemon data is missing the `id` field.');
}

$attributes = $data['attributes'] ?? null;
if (! is_array($attributes)) {
throw new InvalidArgumentException('Daemon data is missing the `attributes` object.');
}

$processes = $attributes['processes'] ?? null;
if (! is_int($processes)) {
throw new InvalidArgumentException('Daemon `attributes.processes` must be an integer.');
}

return new self(
id: $id,
command: self::requireString($attributes, 'command'),
user: self::requireString($attributes, 'user'),
directory: self::optionalString($attributes, 'directory'),
processes: $processes,
status: self::requireString($attributes, 'status'),
createdAt: self::requireDate($attributes, 'created_at'),
);
}

/**
* @param array<array-key, mixed> $attributes
*/
private static function requireString(array $attributes, string $key): string
{
$value = $attributes[$key] ?? null;
if (! is_string($value)) {
throw new InvalidArgumentException(sprintf('Daemon `attributes.%s` must be a string.', $key));
}

return $value;
}

/**
* @param array<array-key, mixed> $attributes
*/
private static function optionalString(array $attributes, string $key): ?string
{
$value = $attributes[$key] ?? null;
if ($value === null) {
return null;
}

if (! is_string($value)) {
throw new InvalidArgumentException(sprintf('Daemon `attributes.%s` must be a string or null.', $key));
}

return $value;
}

/**
* @param array<array-key, mixed> $attributes
*/
private static function requireDate(array $attributes, string $key): DateTimeImmutable
{
$value = $attributes[$key] ?? null;
if (! is_string($value)) {
throw new InvalidArgumentException(sprintf('Daemon `attributes.%s` must be a string.', $key));
}

try {
return new DateTimeImmutable($value);
} catch (Throwable $throwable) {
throw new InvalidArgumentException(sprintf('Daemon `attributes.%s` is not a valid date-time.', $key), 0, $throwable);
}
}

/**
* @return array{id: string, command: string, user: string, directory: ?string, processes: int, status: string, created_at: string}
*/
#[Override]
public function jsonSerialize(): array
{
return [
'id' => $this->id,
'command' => $this->command,
'user' => $this->user,
'directory' => $this->directory,
'processes' => $this->processes,
'status' => $this->status,
'created_at' => $this->createdAt->format(DATE_ATOM),
];
}
}
51 changes: 51 additions & 0 deletions src/Data/ListDaemonsOptions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace PhpDevKits\ForgeSdk\Data;

final readonly class ListDaemonsOptions
{
public function __construct(
public ?int $size = null,
public ?string $cursor = null,
public ?string $sort = null,
public ?string $user = null,
public ?int $siteId = null,
public ?string $directory = null,
) {}

/**
* @return array<string, int|string>
*/
public function toQuery(): array
{
$query = [];

if ($this->size !== null) {
$query['page[size]'] = $this->size;
}

if ($this->cursor !== null) {
$query['page[cursor]'] = $this->cursor;
}

if ($this->sort !== null) {
$query['sort'] = $this->sort;
}

if ($this->user !== null) {
$query['filter[user]'] = $this->user;
}

if ($this->siteId !== null) {
$query['filter[site_id]'] = $this->siteId;
}

if ($this->directory !== null) {
$query['filter[directory]'] = $this->directory;
}

return $query;
}
}
33 changes: 33 additions & 0 deletions src/Data/UpdateDaemonData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace PhpDevKits\ForgeSdk\Data;

/**
* Input payload for PUT /orgs/{org}/servers/{server}/background-processes/{id}.
*
* Forge requires `name` on update; `config` (raw supervisor config) is
* optional.
*/
final readonly class UpdateDaemonData
{
public function __construct(
public string $name,
public ?string $config = null,
) {}

/**
* @return array<string, string>
*/
public function toArray(): array
{
$payload = ['name' => $this->name];

if ($this->config !== null) {
$payload['config'] = $this->config;
}

return $payload;
}
}
19 changes: 19 additions & 0 deletions src/Enums/DaemonAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace PhpDevKits\ForgeSdk\Enums;

/**
* Actions that can be triggered on a daemon via
* POST /background-processes/{id}/actions.
*
* Mirrors the `BackgroundProcessAction` enum in Forge's OpenAPI spec.
*/
enum DaemonAction: string
{
case Restart = 'restart';
case Stop = 'stop';
case Start = 'start';
case EmptyLog = 'empty-log';
}
16 changes: 16 additions & 0 deletions src/Enums/DaemonUser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace PhpDevKits\ForgeSdk\Enums;

/**
* System users a daemon (background process) can run as.
*
* Mirrors the `user` enum on Forge's CreateBackgroundProcessRequest.
*/
enum DaemonUser: string
{
case Root = 'root';
case Forge = 'forge';
}
39 changes: 39 additions & 0 deletions src/Requests/Servers/CreateDaemon.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace PhpDevKits\ForgeSdk\Requests\Servers;

use Override;
use PhpDevKits\ForgeSdk\Data\CreateDaemonData;
use Saloon\Contracts\Body\HasBody;
use Saloon\Enums\Method;
use Saloon\Http\Request;
use Saloon\Traits\Body\HasJsonBody;

final class CreateDaemon extends Request implements HasBody
{
use HasJsonBody;

protected Method $method = Method::POST;

public function __construct(
private readonly string $organization,
private readonly int|string $serverId,
private readonly CreateDaemonData $data,
) {}

#[Override]
public function resolveEndpoint(): string
{
return sprintf('/orgs/%s/servers/%s/background-processes', $this->organization, $this->serverId);
}

/**
* @return array<string, mixed>
*/
protected function defaultBody(): array
{
return $this->data->toArray();
}
}
26 changes: 26 additions & 0 deletions src/Requests/Servers/DeleteDaemon.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace PhpDevKits\ForgeSdk\Requests\Servers;

use Override;
use Saloon\Enums\Method;
use Saloon\Http\Request;

final class DeleteDaemon extends Request
{
protected Method $method = Method::DELETE;

public function __construct(
private readonly string $organization,
private readonly int|string $serverId,
private readonly int|string $daemonId,
) {}

#[Override]
public function resolveEndpoint(): string
{
return sprintf('/orgs/%s/servers/%s/background-processes/%s', $this->organization, $this->serverId, $this->daemonId);
}
}
26 changes: 26 additions & 0 deletions src/Requests/Servers/GetDaemon.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace PhpDevKits\ForgeSdk\Requests\Servers;

use Override;
use Saloon\Enums\Method;
use Saloon\Http\Request;

final class GetDaemon extends Request
{
protected Method $method = Method::GET;

public function __construct(
private readonly string $organization,
private readonly int|string $serverId,
private readonly int|string $daemonId,
) {}

#[Override]
public function resolveEndpoint(): string
{
return sprintf('/orgs/%s/servers/%s/background-processes/%s', $this->organization, $this->serverId, $this->daemonId);
}
}
Loading
Loading