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,
|
||||
* 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<string, RouteConfig|ImportConfig|AliasConfig>,
|
||||
* "when@prod"?: array<string, RouteConfig|ImportConfig|AliasConfig>,
|
||||
* "when@test"?: array<string, RouteConfig|ImportConfig|AliasConfig>,
|
||||
* ...<string, RouteConfig|ImportConfig|AliasConfig>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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