release: dev -> main v0.2.0 #93

Merged
Corentin merged 96 commits from dev into main 2026-06-23 10:09:58 +02:00
5 changed files with 121 additions and 2 deletions
Showing only changes of commit 7ab9a5a8cf - Show all commits

View file

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace App\Catalogue;
use App\Core\DatabaseInterface;
/**
* Lecture des allergenes a declaration obligatoire (INCO) : info GENERALE (les 14
* categories), pas un calcul par produit (le mapping ingredient_allergen reste
* differe). Sert l'endpoint public anonyme /api/allergens. Le schema ne porte que
* code + name ; les descriptions riches restent cote borne (data/allergens.json).
*
* Non `final` : seam de test (sous-classe -> double sans base).
*/
class AllergenRepository
{
public function __construct(private readonly DatabaseInterface $db)
{
}
/**
* Les allergenes references, tries par id (ordre INCO du seed).
*
* @return list<array<string, mixed>>
*/
public function all(): array
{
return $this->db->fetchAll('SELECT id, code, name FROM allergen ORDER BY id');
}
}

View file

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Controllers;
use App\Catalogue\AllergenRepository;
use App\Catalogue\CategoryRepository;
use App\Catalogue\MenuRepository;
use App\Catalogue\ProductRepository;
@ -111,6 +112,21 @@ class CatalogueController extends Controller
return $this->json(['data' => $menu]);
}
/**
* Allergenes INCO (info generale, 14 categories). Public anonyme, lecture seule.
*
* @param array<string, string> $params
*/
public function allergens(array $params = []): Response
{
$rows = array_map(
fn (array $row): array => $this->presentAllergen($row),
$this->allergensRepo()->all(),
);
return $this->json(['data' => $rows, 'total' => count($rows)]);
}
protected function categoriesRepo(): CategoryRepository
{
return new CategoryRepository($this->db());
@ -126,6 +142,11 @@ class CatalogueController extends Controller
return new MenuRepository($this->db());
}
protected function allergensRepo(): AllergenRepository
{
return new AllergenRepository($this->db());
}
/**
* Acces BDD comme DatabaseInterface (seam de test). Database l'implemente.
*/
@ -134,6 +155,19 @@ class CatalogueController extends Controller
return $this->database;
}
/**
* @param array<string, mixed> $row
* @return array{id: int, code: string, name: string}
*/
private function presentAllergen(array $row): array
{
return [
'id' => (int) ($row['id'] ?? 0),
'code' => (string) ($row['code'] ?? ''),
'name' => (string) ($row['name'] ?? ''),
];
}
/**
* @param array<string, mixed> $row
* @return array{id: int, name: string, slug: string, image_path: ?string, display_order: int}

View file

@ -99,6 +99,9 @@ try {
// Menus composes : liste legere + detail avec slots (B1 burger impose, B2 Normal/Maxi).
$router->add('GET', '/api/menus', [CatalogueController::class, 'menus']);
$router->add('GET', '/api/menus/{id}', [CatalogueController::class, 'menu']);
// Allergenes INCO (info generale, 14 categories). La borne garde son JSON statique
// (descriptions riches) ; l'endpoint sert d'autres consommateurs eventuels.
$router->add('GET', '/api/allergens', [CatalogueController::class, 'allergens']);
// Back-office (P3) : pages rendues serveur sous /admin, gardees par SessionGuard.
$router->add('GET', '/admin/dashboard', [DashboardController::class, 'index']);

View file

@ -17,8 +17,10 @@
const CATEGORIES_URL = '/api/categories';
const PRODUCTS_URL = '/api/products';
const MENUS_URL = '/api/menus';
/* Liste fixe des 14 allergenes INCO (info generale, modale borne). Repli statique
* encore en place : bascule sur '/api/allergens' differee. */
/* Liste fixe des 14 allergenes INCO (info generale, modale borne). L'endpoint
* /api/allergens existe desormais (id/code/name), mais la borne garde ce JSON
* statique : il porte les DESCRIPTIONS riches, absentes du schema allergen. Bascule
* possible si les descriptions sont ajoutees cote API. */
const ALLERGENS_URL = 'data/allergens.json';
/* Memoisation par PROMESSE (pas par resultat) : N appelants concurrents au meme

View file

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace App\Tests\Integration;
use PHPUnit\Framework\TestCase;
use Throwable;
use App\Catalogue\AllergenRepository;
use App\Core\Config;
use App\Core\Database;
/**
* AllergenRepository contre une vraie MariaDB (schema migre + seed reference).
* Auto-skip si WAKDO_DB_TESTS != 1. Lecture seule (donnees de reference) : aucun
* fixture/teardown. Verifie que les 14 allergenes INCO sont references avec code+name.
*/
final class AllergenReadDbTest extends TestCase
{
private Database $db;
protected function setUp(): void
{
if (getenv('WAKDO_DB_TESTS') !== '1') {
self::markTestSkipped('Tests DB desactives (definir WAKDO_DB_TESTS=1 + DB_*).');
}
$this->db = new Database(new Config());
try {
$this->db->fetch('SELECT 1');
} catch (Throwable $exception) {
self::markTestSkipped('Base injoignable: ' . $exception->getMessage());
}
}
public function testListsIncoReferenceWithCodeAndName(): void
{
$rows = (new AllergenRepository($this->db))->all();
self::assertGreaterThanOrEqual(14, count($rows), 'les 14 allergenes INCO doivent etre references');
foreach ($rows as $a) {
self::assertArrayHasKey('code', $a);
self::assertArrayHasKey('name', $a);
self::assertNotSame('', (string) ($a['name'] ?? ''));
}
}
}