api: Mahlzeiten GET/POST + checkedIn/checkedOut (X-Api-Token)
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\UI\Http\Api;
|
||||
|
||||
use App\Logic\Essen\Dto\MahlzeitCreateRequest;
|
||||
use App\Logic\Essen\Dto\MahlzeitListRequest;
|
||||
use App\Logic\Essen\Model\MahlzeitStatus;
|
||||
use App\Logic\Essen\Model\Wochentag;
|
||||
use App\Logic\Essen\Query\MahlzeitListQuery;
|
||||
use App\Logic\Essen\UseCase\MahlzeitCheckOffUseCase;
|
||||
use App\Logic\Essen\UseCase\MahlzeitCreateUseCase;
|
||||
use App\Logic\Essen\UseCase\MahlzeitUncheckUseCase;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
|
||||
/**
|
||||
* Stateless JSON-API für Consumer wie Home Assistant (Auth via `X-Api-Token`,
|
||||
* s. `ApiTokenSubscriber`).
|
||||
*
|
||||
* - `GET /api/mahlzeiten` – Liste, optional `?status=geplant|gegessen`.
|
||||
* - `POST /api/mahlzeiten/{id}/checkoff` – abhaken (geplant → gegessen).
|
||||
* - `POST /api/mahlzeiten/{id}/uncheck` – aufheben (gegessen → geplant).
|
||||
* - `POST /api/mahlzeiten` – anlegen (JSON `name` Pflicht, `notiz`/`wochentage` optional).
|
||||
*
|
||||
* Fachregeln laufen über dieselben UseCases/Queries wie die Web-UI;
|
||||
* Fehler werden vom `ExceptionSubscriber` als JSON gerendert.
|
||||
*/
|
||||
final readonly class MahlzeitApiController
|
||||
{
|
||||
public function __construct(
|
||||
private MahlzeitListQuery $list,
|
||||
private MahlzeitCheckOffUseCase $checkOff,
|
||||
private MahlzeitUncheckUseCase $uncheck,
|
||||
private MahlzeitCreateUseCase $create,
|
||||
) {}
|
||||
|
||||
/** @param non-empty-string $name – für die Route-Signatur, hier nur Doku */
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$status = null;
|
||||
$filter = (string) $request->query->get('status', '');
|
||||
if ($filter !== '') {
|
||||
$status = MahlzeitStatus::tryFrom($filter);
|
||||
if ($status === null) {
|
||||
throw new BadRequestHttpException('status muss "geplant" oder "gegessen" sein.');
|
||||
}
|
||||
}
|
||||
|
||||
$items = $this->list->execute(new MahlzeitListRequest(status: $status))->items;
|
||||
|
||||
return new JsonResponse([
|
||||
'mahlzeiten' => \array_map(self::serialize(...), $items),
|
||||
]);
|
||||
}
|
||||
|
||||
public function checkoff(int $id): JsonResponse
|
||||
{
|
||||
return new JsonResponse(self::serialize($this->checkOff->execute($id)));
|
||||
}
|
||||
|
||||
public function uncheck(int $id): JsonResponse
|
||||
{
|
||||
return new JsonResponse(self::serialize($this->uncheck->execute($id)));
|
||||
}
|
||||
|
||||
public function create(Request $request): JsonResponse
|
||||
{
|
||||
$payload = json_decode((string) $request->getContent(), true);
|
||||
if (!\is_array($payload)) {
|
||||
throw new BadRequestHttpException('JSON-Body erwartet, z. B. {"name": "Bockbrot"}');
|
||||
}
|
||||
|
||||
$name = \trim((string) ($payload['name'] ?? ''));
|
||||
if ($name === '') {
|
||||
throw new BadRequestHttpException('name ist ein Pflichtfeld.');
|
||||
}
|
||||
|
||||
$notiz = \trim((string) ($payload['notiz'] ?? ''));
|
||||
$wochentage = Wochentag::ausWerten((array) ($payload['wochentage'] ?? []));
|
||||
|
||||
return new JsonResponse(self::serialize($this->create->execute(new MahlzeitCreateRequest(
|
||||
name: $name,
|
||||
notiz: $notiz,
|
||||
wochentage: $wochentage,
|
||||
))->mahlzeit), 201);
|
||||
}
|
||||
|
||||
/** Serialisiert ein Read-Modell in das API-Format. */
|
||||
public static function serialize(object $m): array
|
||||
{
|
||||
return [
|
||||
'id' => $m->id,
|
||||
'name' => $m->name,
|
||||
'notiz' => $m->notiz,
|
||||
'wochentage' => \array_map(static fn (Wochentag $t): string => $t->value, $m->wochentage),
|
||||
'status' => $m->status->value,
|
||||
'sort_order' => $m->sortOrder,
|
||||
'gegessen_am' => $m->gegessenAm?->format(\DateTimeInterface::RFC3339),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user