api: Mahlzeiten GET/POST + checkedIn/checkedOut (X-Api-Token)
This commit is contained in:
@@ -946,6 +946,15 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
|||||||
* twig?: TwigConfig,
|
* twig?: TwigConfig,
|
||||||
* doctrine?: DoctrineConfig,
|
* doctrine?: DoctrineConfig,
|
||||||
* doctrine_migrations?: DoctrineMigrationsConfig,
|
* doctrine_migrations?: DoctrineMigrationsConfig,
|
||||||
|
* "when@dev"?: array{
|
||||||
|
* imports?: ImportsConfig,
|
||||||
|
* parameters?: ParametersConfig,
|
||||||
|
* services?: ServicesConfig,
|
||||||
|
* framework?: FrameworkConfig,
|
||||||
|
* twig?: TwigConfig,
|
||||||
|
* doctrine?: DoctrineConfig,
|
||||||
|
* doctrine_migrations?: DoctrineMigrationsConfig,
|
||||||
|
* },
|
||||||
* "when@prod"?: array{
|
* "when@prod"?: array{
|
||||||
* imports?: ImportsConfig,
|
* imports?: ImportsConfig,
|
||||||
* parameters?: ParametersConfig,
|
* parameters?: ParametersConfig,
|
||||||
@@ -1046,6 +1055,7 @@ namespace Symfony\Component\Routing\Loader\Configurator;
|
|||||||
* deprecated?: array{package:string, version:string, message?:string},
|
* deprecated?: array{package:string, version:string, message?:string},
|
||||||
* }
|
* }
|
||||||
* @psalm-type RoutesConfig = array{
|
* @psalm-type RoutesConfig = array{
|
||||||
|
* "when@dev"?: array<string, RouteConfig|ImportConfig|AliasConfig>,
|
||||||
* "when@prod"?: array<string, RouteConfig|ImportConfig|AliasConfig>,
|
* "when@prod"?: array<string, RouteConfig|ImportConfig|AliasConfig>,
|
||||||
* "when@test"?: array<string, RouteConfig|ImportConfig|AliasConfig>,
|
* "when@test"?: array<string, RouteConfig|ImportConfig|AliasConfig>,
|
||||||
* ...<string, RouteConfig|ImportConfig|AliasConfig>
|
* ...<string, RouteConfig|ImportConfig|AliasConfig>
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
# yaml-language-server: $schema=https://symfony.com/schema/config/routing/routing.yaml
|
# yaml-language-server: $schema=https://symfony.com/schema/config/routing/routing.yaml
|
||||||
|
|
||||||
|
import:
|
||||||
|
resource: routes/api.yaml
|
||||||
|
|
||||||
home:
|
home:
|
||||||
path: /
|
path: /
|
||||||
controller: App\UI\Http\HomeController::index
|
controller: App\UI\Http\HomeController::index
|
||||||
|
|||||||
@@ -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+
|
||||||
@@ -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