homeos/app/Services/AddonService.php

88 lines
2.6 KiB
PHP

<?php
namespace App\Services;
use App\Models\Addon;
use Illuminate\Support\Facades\DB;
/**
* Install/uninstall integrations and reflect their backend connection status. Addons are opt-in:
* installing one only records intent + config here — the actual bridge (e.g. the ring-mqtt
* sidecar) runs as its own container and reports health over MQTT.
*/
class AddonService
{
public function row(string $key): Addon
{
return Addon::firstOrCreate(['key' => $key]);
}
public function install(string $key): Addon
{
if (! AddonRegistry::has($key)) {
abort(404);
}
$addon = $this->row($key);
$addon->forceFill(['installed' => true])->save();
return $addon;
}
public function uninstall(string $key): void
{
$addon = Addon::where('key', $key)->first();
// Wipe stored secrets on uninstall — no orphaned tokens left encrypted at rest.
$addon?->forceFill([
'installed' => false,
'status' => 'disconnected',
'status_detail' => null,
'config' => null,
'connected_at' => null,
])->save();
}
/**
* Persist the credentials the backend bridge needs. We do NOT talk to Ring ourselves
* (no local API; the ring-mqtt sidecar owns the OAuth + 2FA dance) — we only hand the
* bridge what it asked for and mark the addon as connecting.
*
* @param array<string, mixed> $config
*/
public function saveConnection(string $key, array $config): Addon
{
$addon = $this->install($key);
$existing = $addon->config ?? [];
// Drop blank fields so re-submitting without the password doesn't wipe a stored one.
$merged = array_merge($existing, array_filter($config, fn ($v) => $v !== null && $v !== ''));
$addon->forceFill([
'config' => $merged,
'status' => 'connecting',
'status_detail' => null,
])->save();
return $addon;
}
/** Reflect a status line the bridge published over MQTT (online/offline/error). */
public function markStatus(string $key, string $status, ?string $detail = null): void
{
$addon = Addon::where('key', $key)->where('installed', true)->first();
if ($addon === null) {
return;
}
DB::transaction(function () use ($addon, $status, $detail) {
$addon->forceFill([
'status' => $status,
'status_detail' => $detail,
'connected_at' => $status === 'connected' ? now() : $addon->connected_at,
])->save();
});
}
}