feat: protect production Snipe-IT operations
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Integrations\SnipeIt;
|
||||
|
||||
use App\Integrations\SnipeIt\Data\AssetData;
|
||||
use App\Integrations\SnipeIt\Data\HardwarePage;
|
||||
use App\Integrations\SnipeIt\Exceptions\SnipeItApiException;
|
||||
use App\Integrations\SnipeIt\Exceptions\SnipeItConfigurationException;
|
||||
@@ -16,6 +17,8 @@ use Throwable;
|
||||
|
||||
final class SnipeItClient
|
||||
{
|
||||
public function __construct(private readonly SnipeItAssetMapper $mapper) {}
|
||||
|
||||
public function isConfigured(): bool
|
||||
{
|
||||
return filled(config('services.snipe_it.url')) && filled(config('services.snipe_it.read_token'));
|
||||
@@ -98,6 +101,27 @@ final class SnipeItClient
|
||||
} while ($received > 0 && $offset < $page->total);
|
||||
}
|
||||
|
||||
public function getHardware(int $hardwareId): AssetData
|
||||
{
|
||||
$this->assertPositiveIdentifiers($hardwareId);
|
||||
|
||||
$response = $this->readRequest()->get("/hardware/{$hardwareId}");
|
||||
if (! $response->successful()) {
|
||||
throw new SnipeItApiException(
|
||||
"O Snipe-IT respondeu com HTTP {$response->status()} ao consultar o equipamento.",
|
||||
$response->status(),
|
||||
);
|
||||
}
|
||||
|
||||
$payload = $response->json();
|
||||
if (! is_array($payload) || ($payload['status'] ?? null) === 'error') {
|
||||
throw new SnipeItApiException('O Snipe-IT não devolveu um estado válido para o equipamento.');
|
||||
}
|
||||
|
||||
/** @var array<string, mixed> $payload */
|
||||
return $this->mapper->map($payload);
|
||||
}
|
||||
|
||||
/** @return array<string, mixed> */
|
||||
public function checkoutHardware(
|
||||
int $hardwareId,
|
||||
@@ -108,6 +132,13 @@ final class SnipeItClient
|
||||
): array {
|
||||
$this->assertPositiveIdentifiers($hardwareId, $statusId, $assignedUserId);
|
||||
|
||||
$current = $this->getHardware($hardwareId);
|
||||
if ($current->isAssigned()) {
|
||||
throw new SnipeItApiException(
|
||||
"O equipamento {$current->assetTag} já está atribuído no Snipe-IT; o checkout não foi executado.",
|
||||
);
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'status_id' => $statusId,
|
||||
'checkout_to_type' => 'user',
|
||||
@@ -128,15 +159,23 @@ final class SnipeItClient
|
||||
public function checkinHardware(
|
||||
int $hardwareId,
|
||||
int $statusId,
|
||||
int $expectedAssignedUserId,
|
||||
?int $locationId = null,
|
||||
?string $note = null,
|
||||
): array {
|
||||
$this->assertPositiveIdentifiers($hardwareId, $statusId);
|
||||
$this->assertPositiveIdentifiers($hardwareId, $statusId, $expectedAssignedUserId);
|
||||
|
||||
if ($locationId !== null && $locationId < 1) {
|
||||
throw new SnipeItConfigurationException('O identificador da localização do Snipe-IT tem de ser positivo.');
|
||||
}
|
||||
|
||||
$current = $this->getHardware($hardwareId);
|
||||
if (! $current->isAssignedToUser($expectedAssignedUserId)) {
|
||||
throw new SnipeItApiException(
|
||||
"O equipamento {$current->assetTag} não está atribuído ao destinatário esperado; o check-in não foi executado.",
|
||||
);
|
||||
}
|
||||
|
||||
$payload = ['status_id' => $statusId];
|
||||
|
||||
if ($locationId !== null) {
|
||||
|
||||
Reference in New Issue
Block a user