From 9c148a1242c2cc11e83a7d42c44497d388e8152f Mon Sep 17 00:00:00 2001 From: Jens Date: Fri, 25 Sep 2026 19:06:08 +0200 Subject: [PATCH] api: Mahlzeiten GET/POST + checkedIn/checkedOut (X-Api-Token) --- config/reference.php | 10 +++ config/routes.yaml | 3 + config/routes/api.yaml | 26 ++++++ src/UI/Http/Api/MahlzeitApiController.php | 104 ++++++++++++++++++++++ 4 files changed, 143 insertions(+) create mode 100644 config/routes/api.yaml create mode 100644 src/UI/Http/Api/MahlzeitApiController.php diff --git a/config/reference.php b/config/reference.php index 1b1bd3d..81c04d3 100644 --- a/config/reference.php +++ b/config/reference.php @@ -946,6 +946,15 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * twig?: TwigConfig, * doctrine?: DoctrineConfig, * doctrine_migrations?: DoctrineMigrationsConfig, + * "when@dev"?: array{ + * imports?: ImportsConfig, + * parameters?: ParametersConfig, + * services?: ServicesConfig, + * framework?: FrameworkConfig, + * twig?: TwigConfig, + * doctrine?: DoctrineConfig, + * doctrine_migrations?: DoctrineMigrationsConfig, + * }, * "when@prod"?: array{ * imports?: ImportsConfig, * parameters?: ParametersConfig, @@ -1046,6 +1055,7 @@ namespace Symfony\Component\Routing\Loader\Configurator; * deprecated?: array{package:string, version:string, message?:string}, * } * @psalm-type RoutesConfig = array{ + * "when@dev"?: array, * "when@prod"?: array, * "when@test"?: array, * ... diff --git a/config/routes.yaml b/config/routes.yaml index 5db88a7..0302cf3 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -1,5 +1,8 @@ # yaml-language-server: $schema=https://symfony.com/schema/config/routing/routing.yaml +import: + resource: routes/api.yaml + home: path: / controller: App\UI\Http\HomeController::index diff --git a/config/routes/api.yaml b/config/routes/api.yaml new file mode 100644 index 0000000..b7cbeb8 --- /dev/null +++ b/config/routes/api.yaml @@ -0,0 +1,26 @@ +# yaml-language-server: $schema=https://symfony.com/schema/config/routing/routing.yaml +# Stateless-JSON-API für HA & Co. (Auth: X-Api-Token, s. ApiTokenSubscriber) + +api_mahlzeiten_liste: + path: /api/mahlzeiten + controller: App\UI\Http\Api\MahlzeitApiController::index + methods: [GET] + +api_mahlzeiten_create: + path: /api/mahlzeiten + controller: App\UI\Http\Api\MahlzeitApiController::create + methods: [POST] + +api_mahlzeiten_checkoff: + path: /api/mahlzeiten/{id}/checkoff + controller: App\UI\Http\Api\MahlzeitApiController::checkoff + methods: [POST] + requirements: + id: \d+ + +api_mahlzeiten_uncheck: + path: /api/mahlzeiten/{id}/uncheck + controller: App\UI\Http\Api\MahlzeitApiController::uncheck + methods: [POST] + requirements: + id: \d+ diff --git a/src/UI/Http/Api/MahlzeitApiController.php b/src/UI/Http/Api/MahlzeitApiController.php new file mode 100644 index 0000000..ce073ca --- /dev/null +++ b/src/UI/Http/Api/MahlzeitApiController.php @@ -0,0 +1,104 @@ +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), + ]; + } +}