Files
pictureFrame-webApp/src/Controller/DeviceApiController.php
T
football2801 12245759ac
CI / test (push) Has been cancelled
chore: stage all in-progress work before repo split
Web app: new entities (Image, RenderedAsset, SharedImage, Token,
DeviceImageHistory), enums, repositories, controllers, message handlers,
migrations, tests, frontend upload/library/sticker UI, Vue components.

Firmware: EPD background screen binaries + gen scripts, setup_bg header.

Infra: ddev config, test bundle, gitignore coverage dir.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 12:11:31 -04:00

155 lines
5.6 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Controller;
use App\Entity\Device;
use App\Entity\Image;
use App\Entity\User;
use App\Enum\Orientation;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route('/api/devices')]
#[IsGranted('ROLE_USER')]
class DeviceApiController extends AbstractController
{
#[Route('', name: 'api_devices_list', methods: ['GET'])]
public function list(EntityManagerInterface $em): JsonResponse
{
/** @var User $user */
$user = $this->getUser();
$devices = $em->getRepository(Device::class)->findBy(['user' => $user]);
return $this->json(array_map($this->serialize(...), $devices));
}
#[Route('/{id}', name: 'api_device_update', methods: ['PATCH'])]
public function update(int $id, Request $request, EntityManagerInterface $em): JsonResponse
{
/** @var User $user */
$user = $this->getUser();
$device = $em->getRepository(Device::class)->findOneBy(['id' => $id, 'user' => $user]);
if (!$device) {
return $this->json(['error' => 'Device not found'], Response::HTTP_NOT_FOUND);
}
$body = json_decode($request->getContent(), true) ?? [];
if (isset($body['name'])) {
$name = trim((string) $body['name']);
if (empty($name)) {
return $this->json(['error' => 'Name cannot be empty'], Response::HTTP_UNPROCESSABLE_ENTITY);
}
$device->setName($name);
}
if (isset($body['orientation'])) {
$orientation = Orientation::tryFrom($body['orientation']);
if (!$orientation) {
return $this->json(['error' => 'Invalid orientation'], Response::HTTP_UNPROCESSABLE_ENTITY);
}
$device->setOrientation($orientation);
}
if (isset($body['rotationIntervalMinutes'])) {
$device->setRotationIntervalMinutes(max(1, (int) $body['rotationIntervalMinutes']));
}
if (array_key_exists('wakeHour', $body)) {
$device->setWakeHour($body['wakeHour'] === null ? null : (int) $body['wakeHour']);
}
if (isset($body['timezone'])) {
try {
new \DateTimeZone((string) $body['timezone']);
$device->setTimezone((string) $body['timezone']);
} catch (\Exception) {
return $this->json(['error' => 'Invalid timezone identifier'], Response::HTTP_UNPROCESSABLE_ENTITY);
}
}
if (isset($body['uniquenessWindow'])) {
$device->setUniquenessWindow(max(1, (int) $body['uniquenessWindow']));
}
$em->flush();
return $this->json($this->serialize($device));
}
#[Route('/{id}/lock', name: 'api_device_lock', methods: ['PUT'])]
public function lock(int $id, Request $request, EntityManagerInterface $em): JsonResponse
{
/** @var User $user */
$user = $this->getUser();
$device = $em->getRepository(Device::class)->findOneBy(['id' => $id, 'user' => $user]);
if (!$device) {
return $this->json(['error' => 'Device not found'], Response::HTTP_NOT_FOUND);
}
$body = json_decode($request->getContent(), true) ?? [];
$imageId = $body['imageId'] ?? null;
if (!$imageId) {
return $this->json(['error' => 'imageId required'], Response::HTTP_UNPROCESSABLE_ENTITY);
}
$image = $em->getRepository(Image::class)->find($imageId);
if (!$image || $image->getUser() !== $user) {
return $this->json(['error' => 'Image not found'], Response::HTTP_NOT_FOUND);
}
if (!$image->isApprovedForDevice($device)) {
return $this->json(['error' => 'Image is not approved for this device'], Response::HTTP_UNPROCESSABLE_ENTITY);
}
$device->setLockedImage($image);
$em->flush();
return $this->json($this->serialize($device));
}
#[Route('/{id}/lock', name: 'api_device_unlock', methods: ['DELETE'])]
public function unlock(int $id, EntityManagerInterface $em): JsonResponse
{
/** @var User $user */
$user = $this->getUser();
$device = $em->getRepository(Device::class)->findOneBy(['id' => $id, 'user' => $user]);
if (!$device) {
return $this->json(['error' => 'Device not found'], Response::HTTP_NOT_FOUND);
}
$device->setLockedImage(null);
$em->flush();
return $this->json($this->serialize($device));
}
private function serialize(Device $d): array
{
return [
'id' => $d->getId(),
'mac' => $d->getMac(),
'name' => $d->getName(),
'orientation' => $d->getOrientation()->value,
'rotationIntervalMinutes' => $d->getRotationIntervalMinutes(),
'wakeHour' => $d->getWakeHour(),
'timezone' => $d->getTimezone(),
'uniquenessWindow' => $d->getUniquenessWindow(),
'linkedAt' => $d->getLinkedAt()->format(\DateTimeInterface::ATOM),
'lastSeenAt' => $d->getLastSeenAt()?->format(\DateTimeInterface::ATOM),
'lockedImageId' => $d->getLockedImage()?->getId(),
];
}
}