release: dev -> main v0.2.0 #93
5 changed files with 659 additions and 4 deletions
23
Makefile
23
Makefile
|
|
@ -156,12 +156,12 @@ wait-db: ## Attend que la base de donnees accepte les connexions (timeout 60s)
|
|||
@echo "[wait-db] OK"
|
||||
|
||||
.PHONY: migrate
|
||||
migrate: ## Applique les migrations SQL en attente [a venir]
|
||||
@echo "[migrate] Pas encore implemente. Les migrations seront dans db/migrations/."
|
||||
migrate: ## Applique les migrations SQL en attente (db/migrations/)
|
||||
@bash db/migrate.sh
|
||||
|
||||
.PHONY: seed
|
||||
seed: ## Charge les donnees de demo [a venir]
|
||||
@echo "[seed] Pas encore implemente. Les seeds seront dans db/seeds/."
|
||||
seed: ## Charge les donnees de demo (db/seeds/)
|
||||
@bash db/seed.sh
|
||||
|
||||
.PHONY: backup
|
||||
backup: ## Declenche un dump SQL horodate immediat (via le container cron)
|
||||
|
|
@ -211,6 +211,21 @@ clean: ## Stop + suppression containers + volumes (DESTRUCTIF, demande confirmat
|
|||
clean-force: ## Version non interactive de clean (pour CI uniquement)
|
||||
@$(COMPOSE) down -v
|
||||
|
||||
# === Documentation ===
|
||||
|
||||
.PHONY: docs-render
|
||||
docs-render: ## Regenere les diagrammes Mermaid (docs/**/_diagrams/*.mmd -> *.svg)
|
||||
@echo "[docs-render] Recherche des sources Mermaid sous docs/..."
|
||||
@count=0; \
|
||||
for src in $$(find docs -name '*.mmd' -path '*/_diagrams/*'); do \
|
||||
out="$${src%.mmd}.svg"; \
|
||||
echo " $$src -> $$out"; \
|
||||
npx -y -p @mermaid-js/mermaid-cli mmdc -i "$$src" -o "$$out" >/dev/null 2>&1 \
|
||||
|| { echo "[docs-render] ECHEC sur $$src"; exit 1; }; \
|
||||
count=$$((count + 1)); \
|
||||
done; \
|
||||
echo "[docs-render] $$count diagramme(s) genere(s)."
|
||||
|
||||
# === Hooks Git ===
|
||||
|
||||
.PHONY: install-hooks
|
||||
|
|
|
|||
38
db/README.md
Normal file
38
db/README.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Base de donnees - migrations & seeds
|
||||
|
||||
Transcription executable du MLD (`docs/merise/mld.md`, 21 tables) vers MariaDB 11.4.
|
||||
|
||||
## Arborescence
|
||||
|
||||
```
|
||||
db/
|
||||
migrations/ migrations SQL versionnees, appliquees dans l'ordre lexicographique
|
||||
0001_init_schema.sql schema initial : 21 tables, FK, CHECK, index (InnoDB, utf8mb4)
|
||||
seeds/ donnees de demonstration (a venir : roles/permissions, allergenes, catalogue)
|
||||
migrate.sh runner de migrations (idempotent)
|
||||
```
|
||||
|
||||
## Appliquer les migrations
|
||||
|
||||
```bash
|
||||
bash db/migrate.sh # applique les migrations en attente
|
||||
bash db/migrate.sh --status # liste l'etat sans rien appliquer
|
||||
```
|
||||
|
||||
Le runner cible le conteneur `wakdo-db` et lit les identifiants dans `.env`
|
||||
(`DB_NAME`, `DB_ROOT_PASSWORD`). Il maintient une table `schema_migrations`
|
||||
(une ligne par fichier applique) : relancer ne rejoue que les nouvelles
|
||||
migrations. La cible `make migrate` est destinee a appeler ce script.
|
||||
|
||||
## Conventions
|
||||
|
||||
- Une migration = un fichier `NNNN_description.sql`. Un fichier deja applique en
|
||||
commun n'est plus edite : on ajoute une nouvelle migration pour corriger.
|
||||
- Pas de `CREATE DATABASE` / `USE` dans les fichiers : la base cible est choisie
|
||||
par le runner.
|
||||
- Le schema suit le MLD v0.2 a la lettre : montants en centimes (INT UNSIGNED),
|
||||
`vat_rate` en pour-mille, `service_day` NON materialise (calcule applicatif,
|
||||
decision D6), stock signe (survente), journaux append-only (`stock_movement`,
|
||||
`audit_log`).
|
||||
- Verification : le DDL a ete applique sur une instance MariaDB 11.4 reelle
|
||||
(21 tables, 28 FK, 22 CHECK) sans erreur avant integration.
|
||||
68
db/migrate.sh
Executable file
68
db/migrate.sh
Executable file
|
|
@ -0,0 +1,68 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Wakdo - migration runner.
|
||||
#
|
||||
# Applique les fichiers db/migrations/*.sql dans l'ordre lexicographique,
|
||||
# de maniere idempotente : une table schema_migrations enregistre les fichiers
|
||||
# deja appliques, donc relancer ne rejoue que les nouvelles migrations.
|
||||
#
|
||||
# Cible : le service docker-compose `wakdo-db` (MariaDB). Lance depuis l'hote
|
||||
# (c'est ce que `make migrate` appellera). Identifiants lus dans .env.
|
||||
#
|
||||
# Usage :
|
||||
# bash db/migrate.sh # applique les migrations en attente
|
||||
# bash db/migrate.sh --status # liste l'etat sans rien appliquer
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
ENV_FILE="$ROOT/.env"
|
||||
CONTAINER="${WAKDO_DB_CONTAINER:-wakdo-db}"
|
||||
MIGRATIONS_DIR="$ROOT/db/migrations"
|
||||
|
||||
[ -f "$ENV_FILE" ] || { echo "ERREUR : .env introuvable ($ENV_FILE)" >&2; exit 1; }
|
||||
DB_NAME="$(grep -E '^DB_NAME=' "$ENV_FILE" | cut -d= -f2- | tr -d '[:space:]')"
|
||||
DB_ROOT_PASSWORD="$(grep -E '^DB_ROOT_PASSWORD=' "$ENV_FILE" | cut -d= -f2-)"
|
||||
: "${DB_NAME:?DB_NAME absent de .env}"
|
||||
: "${DB_ROOT_PASSWORD:?DB_ROOT_PASSWORD absent de .env}"
|
||||
|
||||
# Client mariadb dans le conteneur (root : les migrations sont des operations DDL).
|
||||
db() { docker exec -i "$CONTAINER" mariadb -uroot -p"$DB_ROOT_PASSWORD" "$@"; }
|
||||
|
||||
# Le conteneur doit etre en marche.
|
||||
docker exec "$CONTAINER" true 2>/dev/null || { echo "ERREUR : conteneur $CONTAINER non demarre (make up)" >&2; exit 1; }
|
||||
|
||||
# Journal des migrations appliquees.
|
||||
db "$DB_NAME" -e "CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||
filename VARCHAR(255) NOT NULL PRIMARY KEY,
|
||||
applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;"
|
||||
|
||||
shopt -s nullglob
|
||||
files=("$MIGRATIONS_DIR"/*.sql)
|
||||
[ ${#files[@]} -gt 0 ] || { echo "[migrate] aucune migration dans $MIGRATIONS_DIR"; exit 0; }
|
||||
|
||||
if [ "${1:-}" = "--status" ]; then
|
||||
echo "[migrate] etat des migrations (base $DB_NAME) :"
|
||||
for f in "${files[@]}"; do
|
||||
base="$(basename "$f")"
|
||||
n="$(db "$DB_NAME" -N -s -e "SELECT COUNT(*) FROM schema_migrations WHERE filename='$base';")"
|
||||
[ "$n" = "0" ] && echo " PENDING $base" || echo " applied $base"
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
|
||||
applied=0
|
||||
for f in "${files[@]}"; do
|
||||
base="$(basename "$f")"
|
||||
n="$(db "$DB_NAME" -N -s -e "SELECT COUNT(*) FROM schema_migrations WHERE filename='$base';")"
|
||||
if [ "$n" = "0" ]; then
|
||||
echo "[migrate] application de $base ..."
|
||||
db "$DB_NAME" < "$f"
|
||||
db "$DB_NAME" -e "INSERT INTO schema_migrations (filename) VALUES ('$base');"
|
||||
applied=$((applied + 1))
|
||||
else
|
||||
echo "[migrate] $base deja applique, ignore"
|
||||
fi
|
||||
done
|
||||
echo "[migrate] termine ($applied nouvelle(s) migration(s) appliquee(s))."
|
||||
465
db/migrations/0001_init_schema.sql
Normal file
465
db/migrations/0001_init_schema.sql
Normal file
|
|
@ -0,0 +1,465 @@
|
|||
-- =============================================================================
|
||||
-- Wakdo — Initial schema (DDL)
|
||||
-- =============================================================================
|
||||
-- Purpose : Create the 21-table relational schema for the Wakdo fast-food
|
||||
-- ordering system (catalogue, ingredients/stock, orders, RBAC,
|
||||
-- security-by-design layer).
|
||||
-- Source : docs/merise/mld.md (MLD v0.2 — prod-like, 21 tables) +
|
||||
-- docs/merise/dictionary.md (data dictionary v0.2, types source of truth).
|
||||
-- Phase : P2 — generated from the validated Logical Data Model (P1 conception).
|
||||
-- Target : MariaDB 11.4 LTS, engine InnoDB, charset utf8mb4, collation
|
||||
-- utf8mb4_unicode_ci.
|
||||
--
|
||||
-- Notes derived from the MLD:
|
||||
-- - All technical PKs are INT UNSIGNED AUTO_INCREMENT.
|
||||
-- - Monetary amounts are INT UNSIGNED in cents (anti-FLOAT, dict. note 1).
|
||||
-- - vat_rate stored per-mille (55 = 5.5%, 100 = 10%).
|
||||
-- - service_day is NOT a stored/generated column (decision D6): computed in
|
||||
-- the application layer.
|
||||
-- - No CREATE DATABASE / USE here: the target DB is chosen by the runner.
|
||||
-- - No seed / INSERT data here (see db/seeds/0001_demo_data.sql).
|
||||
-- =============================================================================
|
||||
|
||||
SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
SET @OLD_SQL_MODE = @@SQL_MODE;
|
||||
SET SQL_MODE = 'STRICT_ALL_TABLES,NO_ENGINE_SUBSTITUTION,NO_AUTO_VALUE_ON_ZERO';
|
||||
SET @OLD_FOREIGN_KEY_CHECKS = @@FOREIGN_KEY_CHECKS;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 4.1 category — root table for the Catalogue sub-domain (no FK)
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE category (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
name VARCHAR(60) NOT NULL,
|
||||
slug VARCHAR(60) NOT NULL,
|
||||
image_path VARCHAR(255) NULL,
|
||||
display_order SMALLINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_category_name (name),
|
||||
UNIQUE KEY uk_category_slug (slug)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 4.6 ingredient — root table for Ingredients & Stock (no FK)
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE ingredient (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
name VARCHAR(120) NOT NULL,
|
||||
unit VARCHAR(40) NOT NULL,
|
||||
stock_quantity INT NOT NULL DEFAULT 0,
|
||||
stock_capacity INT NOT NULL,
|
||||
pack_size SMALLINT UNSIGNED NOT NULL DEFAULT 1,
|
||||
pack_label VARCHAR(80) NULL,
|
||||
low_stock_pct SMALLINT UNSIGNED NOT NULL DEFAULT 10,
|
||||
critical_stock_pct SMALLINT UNSIGNED NOT NULL DEFAULT 5,
|
||||
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_ingredient_name (name),
|
||||
CONSTRAINT chk_ingredient_stock_capacity CHECK (stock_capacity > 0),
|
||||
CONSTRAINT chk_ingredient_pack_size CHECK (pack_size > 0),
|
||||
CONSTRAINT chk_ingredient_low_stock_pct CHECK (low_stock_pct BETWEEN 0 AND 100),
|
||||
CONSTRAINT chk_ingredient_critical_stock_pct CHECK (critical_stock_pct BETWEEN 0 AND 100),
|
||||
CONSTRAINT chk_ingredient_critical_lt_low CHECK (critical_stock_pct < low_stock_pct)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 4.8 allergen — reference table (INCO EU 1169/2011), no FK
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE allergen (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
code VARCHAR(30) NOT NULL,
|
||||
name VARCHAR(80) NOT NULL,
|
||||
description TEXT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_allergen_code (code)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 4.10 role — root table for RBAC (no FK)
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE role (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
code VARCHAR(40) NOT NULL,
|
||||
label VARCHAR(80) NOT NULL,
|
||||
description TEXT NULL,
|
||||
default_route VARCHAR(120) NULL,
|
||||
order_source ENUM('kiosk','counter','drive') NULL,
|
||||
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_role_code (code)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 4.13 permission — reference table, catalogue frozen at 23 codes (no FK)
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE permission (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
code VARCHAR(60) NOT NULL,
|
||||
label VARCHAR(120) NOT NULL,
|
||||
description TEXT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_permission_code (code)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 4.21 login_throttle — per-source-IP brute-force throttle (no FK)
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE login_throttle (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
ip_address VARCHAR(45) NOT NULL,
|
||||
failed_attempts SMALLINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
window_started_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
lockout_until DATETIME NULL,
|
||||
last_attempt_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_login_throttle_ip_address (ip_address),
|
||||
KEY idx_login_throttle_lockout_until (lockout_until)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 4.2 product — depends on category
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE product (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
category_id INT UNSIGNED NOT NULL,
|
||||
name VARCHAR(120) NOT NULL,
|
||||
description TEXT NULL,
|
||||
price_cents INT UNSIGNED NOT NULL,
|
||||
vat_rate SMALLINT UNSIGNED NOT NULL DEFAULT 100,
|
||||
image_path VARCHAR(255) NULL,
|
||||
is_available TINYINT(1) NOT NULL DEFAULT 1,
|
||||
display_order SMALLINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_product_category_available_order (category_id, is_available, display_order),
|
||||
CONSTRAINT fk_product_category_id FOREIGN KEY (category_id)
|
||||
REFERENCES category (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT chk_product_price_cents CHECK (price_cents > 0),
|
||||
CONSTRAINT chk_product_vat_rate CHECK (vat_rate IN (55, 100))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 4.3 menu — depends on category, product
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE menu (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
category_id INT UNSIGNED NOT NULL,
|
||||
burger_product_id INT UNSIGNED NOT NULL,
|
||||
name VARCHAR(120) NOT NULL,
|
||||
description TEXT NULL,
|
||||
price_normal_cents INT UNSIGNED NOT NULL,
|
||||
price_maxi_cents INT UNSIGNED NOT NULL,
|
||||
image_path VARCHAR(255) NULL,
|
||||
is_available TINYINT(1) NOT NULL DEFAULT 1,
|
||||
display_order SMALLINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_menu_category_available_order (category_id, is_available, display_order),
|
||||
CONSTRAINT fk_menu_category_id FOREIGN KEY (category_id)
|
||||
REFERENCES category (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT fk_menu_burger_product_id FOREIGN KEY (burger_product_id)
|
||||
REFERENCES product (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT chk_menu_price_normal_cents CHECK (price_normal_cents > 0),
|
||||
CONSTRAINT chk_menu_price_maxi_cents CHECK (price_maxi_cents > 0)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 4.4 menu_slot — depends on menu (no audit fields)
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE menu_slot (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
menu_id INT UNSIGNED NOT NULL,
|
||||
name VARCHAR(80) NOT NULL,
|
||||
slot_type ENUM('drink','side','sauce','dessert','extra') NOT NULL,
|
||||
is_required TINYINT(1) NOT NULL DEFAULT 1,
|
||||
display_order SMALLINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_menu_slot_menu_order (menu_id, display_order),
|
||||
CONSTRAINT fk_menu_slot_menu_id FOREIGN KEY (menu_id)
|
||||
REFERENCES menu (id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 4.5 menu_slot_option — pure join table, composite PK
|
||||
-- depends on menu_slot, product
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE menu_slot_option (
|
||||
menu_slot_id INT UNSIGNED NOT NULL,
|
||||
product_id INT UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (menu_slot_id, product_id),
|
||||
KEY idx_menu_slot_option_product_id (product_id),
|
||||
CONSTRAINT fk_menu_slot_option_menu_slot_id FOREIGN KEY (menu_slot_id)
|
||||
REFERENCES menu_slot (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_menu_slot_option_product_id FOREIGN KEY (product_id)
|
||||
REFERENCES product (id) ON DELETE RESTRICT
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 4.7 product_ingredient — join table with attributes, composite PK
|
||||
-- depends on product, ingredient
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE product_ingredient (
|
||||
product_id INT UNSIGNED NOT NULL,
|
||||
ingredient_id INT UNSIGNED NOT NULL,
|
||||
quantity_normal SMALLINT UNSIGNED NOT NULL DEFAULT 1,
|
||||
quantity_maxi SMALLINT UNSIGNED NOT NULL DEFAULT 1,
|
||||
is_removable TINYINT(1) NOT NULL DEFAULT 1,
|
||||
is_addable TINYINT(1) NOT NULL DEFAULT 0,
|
||||
extra_price_cents INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (product_id, ingredient_id),
|
||||
KEY idx_product_ingredient_ingredient_id (ingredient_id),
|
||||
CONSTRAINT fk_product_ingredient_product_id FOREIGN KEY (product_id)
|
||||
REFERENCES product (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_product_ingredient_ingredient_id FOREIGN KEY (ingredient_id)
|
||||
REFERENCES ingredient (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT chk_product_ingredient_quantity_normal CHECK (quantity_normal > 0),
|
||||
CONSTRAINT chk_product_ingredient_quantity_maxi CHECK (quantity_maxi >= quantity_normal),
|
||||
CONSTRAINT chk_product_ingredient_extra_price CHECK (extra_price_cents >= 0)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 4.9 ingredient_allergen — pure join table, composite PK
|
||||
-- depends on ingredient, allergen
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE ingredient_allergen (
|
||||
ingredient_id INT UNSIGNED NOT NULL,
|
||||
allergen_id INT UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (ingredient_id, allergen_id),
|
||||
KEY idx_ingredient_allergen_allergen_id (allergen_id),
|
||||
CONSTRAINT fk_ingredient_allergen_ingredient_id FOREIGN KEY (ingredient_id)
|
||||
REFERENCES ingredient (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_ingredient_allergen_allergen_id FOREIGN KEY (allergen_id)
|
||||
REFERENCES allergen (id) ON DELETE RESTRICT
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 4.11 user — depends on role
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE user (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
email VARCHAR(254) NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
pin_hash VARCHAR(255) NULL,
|
||||
first_name VARCHAR(60) NOT NULL,
|
||||
last_name VARCHAR(60) NOT NULL,
|
||||
role_id INT UNSIGNED NOT NULL,
|
||||
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
||||
last_login_at DATETIME NULL,
|
||||
failed_login_attempts SMALLINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
last_failed_login_at DATETIME NULL,
|
||||
lockout_until DATETIME NULL,
|
||||
password_reset_token_hash VARCHAR(255) NULL,
|
||||
password_reset_expires_at DATETIME NULL,
|
||||
anonymized_at DATETIME NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_user_email (email),
|
||||
KEY idx_user_active_role (is_active, role_id),
|
||||
CONSTRAINT fk_user_role_id FOREIGN KEY (role_id)
|
||||
REFERENCES role (id) ON DELETE RESTRICT
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 4.12 role_visible_source — pure join table, composite PK
|
||||
-- depends on role
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE role_visible_source (
|
||||
role_id INT UNSIGNED NOT NULL,
|
||||
source ENUM('kiosk','counter','drive') NOT NULL,
|
||||
PRIMARY KEY (role_id, source),
|
||||
CONSTRAINT fk_role_visible_source_role_id FOREIGN KEY (role_id)
|
||||
REFERENCES role (id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 4.14 role_permission — pure join table, composite PK
|
||||
-- depends on role, permission
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE role_permission (
|
||||
role_id INT UNSIGNED NOT NULL,
|
||||
permission_id INT UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (role_id, permission_id),
|
||||
KEY idx_role_permission_permission_id (permission_id),
|
||||
CONSTRAINT fk_role_permission_role_id FOREIGN KEY (role_id)
|
||||
REFERENCES role (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_role_permission_permission_id FOREIGN KEY (permission_id)
|
||||
REFERENCES permission (id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 4.15 customer_order — depends on user (acting_user_id)
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE customer_order (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
order_number VARCHAR(20) NOT NULL,
|
||||
idempotency_key VARCHAR(36) NULL,
|
||||
source ENUM('kiosk','counter','drive') NOT NULL,
|
||||
acting_user_id INT UNSIGNED NULL,
|
||||
service_mode ENUM('dine_in','takeaway','drive') NOT NULL,
|
||||
status ENUM('pending_payment','paid','delivered','cancelled') NOT NULL DEFAULT 'pending_payment',
|
||||
total_ht_cents INT UNSIGNED NOT NULL,
|
||||
total_vat_cents INT UNSIGNED NOT NULL,
|
||||
total_ttc_cents INT UNSIGNED NOT NULL,
|
||||
paid_at DATETIME NULL,
|
||||
delivered_at DATETIME NULL,
|
||||
cancelled_at DATETIME NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_customer_order_order_number (order_number),
|
||||
UNIQUE KEY uk_customer_order_idempotency_key (idempotency_key),
|
||||
KEY idx_customer_order_status_created (status, created_at),
|
||||
KEY idx_customer_order_source_created (source, created_at),
|
||||
KEY idx_customer_order_created (created_at),
|
||||
CONSTRAINT fk_customer_order_acting_user_id FOREIGN KEY (acting_user_id)
|
||||
REFERENCES user (id) ON DELETE SET NULL,
|
||||
CONSTRAINT chk_customer_order_total_ht CHECK (total_ht_cents >= 0),
|
||||
CONSTRAINT chk_customer_order_total_vat CHECK (total_vat_cents >= 0),
|
||||
CONSTRAINT chk_customer_order_total_ttc CHECK (total_ttc_cents > 0),
|
||||
CONSTRAINT chk_customer_order_total_coherent CHECK (total_ttc_cents = total_ht_cents + total_vat_cents),
|
||||
CONSTRAINT chk_customer_order_drive_mode CHECK (source <> 'drive' OR service_mode = 'drive')
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 4.16 order_item — depends on customer_order, product, menu
|
||||
-- polymorphic line (product XOR menu)
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE order_item (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
order_id INT UNSIGNED NOT NULL,
|
||||
item_type ENUM('product','menu') NOT NULL,
|
||||
product_id INT UNSIGNED NULL,
|
||||
menu_id INT UNSIGNED NULL,
|
||||
format ENUM('normal','maxi') NOT NULL DEFAULT 'normal',
|
||||
label_snapshot VARCHAR(120) NOT NULL,
|
||||
unit_price_cents_snapshot INT UNSIGNED NOT NULL,
|
||||
vat_rate_snapshot SMALLINT UNSIGNED NOT NULL,
|
||||
quantity SMALLINT UNSIGNED NOT NULL DEFAULT 1,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_order_item_order_id (order_id),
|
||||
KEY idx_order_item_product_id (product_id),
|
||||
KEY idx_order_item_menu_id (menu_id),
|
||||
CONSTRAINT fk_order_item_order_id FOREIGN KEY (order_id)
|
||||
REFERENCES customer_order (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_order_item_product_id FOREIGN KEY (product_id)
|
||||
REFERENCES product (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT fk_order_item_menu_id FOREIGN KEY (menu_id)
|
||||
REFERENCES menu (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT chk_order_item_unit_price CHECK (unit_price_cents_snapshot > 0),
|
||||
CONSTRAINT chk_order_item_vat_rate CHECK (vat_rate_snapshot IN (55, 100)),
|
||||
CONSTRAINT chk_order_item_quantity CHECK (quantity > 0),
|
||||
CONSTRAINT chk_order_item_polymorphism CHECK (
|
||||
(item_type = 'product' AND product_id IS NOT NULL AND menu_id IS NULL)
|
||||
OR (item_type = 'menu' AND menu_id IS NOT NULL AND product_id IS NULL)
|
||||
)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 4.17 order_item_selection — depends on order_item, menu_slot, product
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE order_item_selection (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
order_item_id INT UNSIGNED NOT NULL,
|
||||
menu_slot_id INT UNSIGNED NOT NULL,
|
||||
product_id INT UNSIGNED NOT NULL,
|
||||
label_snapshot VARCHAR(120) NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_order_item_selection_order_item_id (order_item_id),
|
||||
KEY idx_order_item_selection_menu_slot_id (menu_slot_id),
|
||||
KEY idx_order_item_selection_product_id (product_id),
|
||||
CONSTRAINT fk_order_item_selection_order_item_id FOREIGN KEY (order_item_id)
|
||||
REFERENCES order_item (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_order_item_selection_menu_slot_id FOREIGN KEY (menu_slot_id)
|
||||
REFERENCES menu_slot (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT fk_order_item_selection_product_id FOREIGN KEY (product_id)
|
||||
REFERENCES product (id) ON DELETE RESTRICT
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 4.18 order_item_modifier — depends on order_item, ingredient
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE order_item_modifier (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
order_item_id INT UNSIGNED NOT NULL,
|
||||
ingredient_id INT UNSIGNED NOT NULL,
|
||||
action ENUM('remove','add') NOT NULL,
|
||||
extra_price_cents INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_order_item_modifier_order_item_id (order_item_id),
|
||||
KEY idx_order_item_modifier_ingredient_id (ingredient_id),
|
||||
CONSTRAINT fk_order_item_modifier_order_item_id FOREIGN KEY (order_item_id)
|
||||
REFERENCES order_item (id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_order_item_modifier_ingredient_id FOREIGN KEY (ingredient_id)
|
||||
REFERENCES ingredient (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT chk_order_item_modifier_extra_price CHECK (extra_price_cents >= 0)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 4.19 stock_movement — append-only audit log
|
||||
-- depends on ingredient, customer_order, user
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE stock_movement (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
ingredient_id INT UNSIGNED NOT NULL,
|
||||
movement_type ENUM('sale','cancellation','restock','inventory_correction') NOT NULL,
|
||||
delta INT NOT NULL,
|
||||
order_id INT UNSIGNED NULL,
|
||||
user_id INT UNSIGNED NULL,
|
||||
note VARCHAR(255) NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_stock_movement_ingredient_created (ingredient_id, created_at),
|
||||
KEY idx_stock_movement_type_created (movement_type, created_at),
|
||||
KEY idx_stock_movement_order_id (order_id),
|
||||
KEY idx_stock_movement_user_id (user_id),
|
||||
CONSTRAINT fk_stock_movement_ingredient_id FOREIGN KEY (ingredient_id)
|
||||
REFERENCES ingredient (id) ON DELETE RESTRICT,
|
||||
CONSTRAINT fk_stock_movement_order_id FOREIGN KEY (order_id)
|
||||
REFERENCES customer_order (id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_stock_movement_user_id FOREIGN KEY (user_id)
|
||||
REFERENCES user (id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 4.20 audit_log — append-only sensitive-action log
|
||||
-- depends on user, role
|
||||
-- -----------------------------------------------------------------------------
|
||||
CREATE TABLE audit_log (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
actor_user_id INT UNSIGNED NULL,
|
||||
actor_role_id INT UNSIGNED NULL,
|
||||
action_code VARCHAR(60) NOT NULL,
|
||||
entity_type VARCHAR(40) NULL,
|
||||
entity_id INT UNSIGNED NULL,
|
||||
summary VARCHAR(255) NULL,
|
||||
details JSON NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_audit_log_actor_created (actor_user_id, created_at),
|
||||
KEY idx_audit_log_entity (entity_type, entity_id),
|
||||
KEY idx_audit_log_action_created (action_code, created_at),
|
||||
KEY idx_audit_log_actor_role_id (actor_role_id),
|
||||
CONSTRAINT fk_audit_log_actor_user_id FOREIGN KEY (actor_user_id)
|
||||
REFERENCES user (id) ON DELETE SET NULL,
|
||||
CONSTRAINT fk_audit_log_actor_role_id FOREIGN KEY (actor_role_id)
|
||||
REFERENCES role (id) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- =============================================================================
|
||||
-- Restore session settings
|
||||
-- =============================================================================
|
||||
SET FOREIGN_KEY_CHECKS = @OLD_FOREIGN_KEY_CHECKS;
|
||||
SET SQL_MODE = @OLD_SQL_MODE;
|
||||
69
db/seed.sh
Executable file
69
db/seed.sh
Executable file
|
|
@ -0,0 +1,69 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Wakdo - seed runner.
|
||||
#
|
||||
# Applique les fichiers db/seeds/*.sql dans l'ordre lexicographique, de maniere
|
||||
# idempotente : une table seed_history enregistre les fichiers deja charges.
|
||||
# Les seeds doivent etre joues APRES les migrations (les tables doivent exister).
|
||||
#
|
||||
# Cible : le service docker-compose `wakdo-db`. Identifiants lus dans .env.
|
||||
#
|
||||
# Usage :
|
||||
# bash db/seed.sh # charge les seeds en attente
|
||||
# bash db/seed.sh --status # liste l'etat sans rien charger
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
ENV_FILE="$ROOT/.env"
|
||||
CONTAINER="${WAKDO_DB_CONTAINER:-wakdo-db}"
|
||||
SEEDS_DIR="$ROOT/db/seeds"
|
||||
|
||||
[ -f "$ENV_FILE" ] || { echo "ERREUR : .env introuvable ($ENV_FILE)" >&2; exit 1; }
|
||||
DB_NAME="$(grep -E '^DB_NAME=' "$ENV_FILE" | cut -d= -f2- | tr -d '[:space:]')"
|
||||
DB_ROOT_PASSWORD="$(grep -E '^DB_ROOT_PASSWORD=' "$ENV_FILE" | cut -d= -f2-)"
|
||||
: "${DB_NAME:?DB_NAME absent de .env}"
|
||||
: "${DB_ROOT_PASSWORD:?DB_ROOT_PASSWORD absent de .env}"
|
||||
|
||||
db() { docker exec -i "$CONTAINER" mariadb -uroot -p"$DB_ROOT_PASSWORD" "$@"; }
|
||||
|
||||
docker exec "$CONTAINER" true 2>/dev/null || { echo "ERREUR : conteneur $CONTAINER non demarre (make up)" >&2; exit 1; }
|
||||
|
||||
if [ ! -d "$SEEDS_DIR" ]; then
|
||||
echo "[seed] aucun repertoire db/seeds/ - rien a charger"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
db "$DB_NAME" -e "CREATE TABLE IF NOT EXISTS seed_history (
|
||||
filename VARCHAR(255) NOT NULL PRIMARY KEY,
|
||||
applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;"
|
||||
|
||||
shopt -s nullglob
|
||||
files=("$SEEDS_DIR"/*.sql)
|
||||
[ ${#files[@]} -gt 0 ] || { echo "[seed] aucun fichier seed dans $SEEDS_DIR"; exit 0; }
|
||||
|
||||
if [ "${1:-}" = "--status" ]; then
|
||||
echo "[seed] etat des seeds (base $DB_NAME) :"
|
||||
for f in "${files[@]}"; do
|
||||
base="$(basename "$f")"
|
||||
n="$(db "$DB_NAME" -N -s -e "SELECT COUNT(*) FROM seed_history WHERE filename='$base';")"
|
||||
[ "$n" = "0" ] && echo " PENDING $base" || echo " loaded $base"
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
|
||||
loaded=0
|
||||
for f in "${files[@]}"; do
|
||||
base="$(basename "$f")"
|
||||
n="$(db "$DB_NAME" -N -s -e "SELECT COUNT(*) FROM seed_history WHERE filename='$base';")"
|
||||
if [ "$n" = "0" ]; then
|
||||
echo "[seed] chargement de $base ..."
|
||||
db "$DB_NAME" < "$f"
|
||||
db "$DB_NAME" -e "INSERT INTO seed_history (filename) VALUES ('$base');"
|
||||
loaded=$((loaded + 1))
|
||||
else
|
||||
echo "[seed] $base deja charge, ignore"
|
||||
fi
|
||||
done
|
||||
echo "[seed] termine ($loaded nouveau(x) seed(s) charge(s))."
|
||||
Loading…
Add table
Reference in a new issue