12245759ac
CI / test (push) Has been cancelled
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>
155 lines
5.6 KiB
PHP
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(),
|
|
];
|
|
}
|
|
}
|