Skip to content

Environnement de développement

Capture des e-mails — Mailpit

En développement, les e-mails ne sont pas envoyés à de vrais destinataires : ils sont interceptés par Mailpit, un serveur SMTP léger doublé d'une interface web. Tout courriel émis par l'application (réinitialisation de mot de passe, notifications, etc.) y est capté et consultable, sans risque de fuite vers l'extérieur.

  • Service : mailpit de docker-compose.yml, image officielle axllent/mailpit. Pas de volume : le stockage en mémoire (défaut) suffit en développement, les messages sont éphémères et repartent à zéro à chaque redémarrage du conteneur.
  • Ports exposés sur l'hôte :
    • 1025 — point d'entrée SMTP (réception du courrier).
    • 8025interface web de consultation.
  • Interface web : http://localhost:8025 — pour lire les e-mails capturés.

Configuration SMTP

L'application est configurée pour livrer via SMTP (.env / .env.example) :

dotenv
MAIL_MAILER=smtp
MAIL_HOST=127.0.0.1
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null

Mailpit n'exige ni authentification ni chiffrement : MAIL_USERNAME, MAIL_PASSWORD et le chiffrement restent nuls.

Hôte selon le contexte d'exécution (hôte vs conteneur)

Le projet se lance de deux façons, et l'hôte SMTP à viser diffère — exactement comme pour la base de données (voir Base de données & identifiants) :

  • Sur l'hôte (php artisan serve lancé directement) : Mailpit est joignable sur 127.0.0.1:1025, le défaut de .env.
  • Dans le conteneur app : 127.0.0.1 désigne le conteneur lui-même, pas Mailpit. Il faut viser le nom de service Docker mailpit. Le bloc environment: du service app dans docker-compose.yml impose donc MAIL_HOST=mailpit (avec MAIL_MAILER=smtp et MAIL_PORT=1025), qui prime sur .env pour les commandes en ligne.

Piège (identique à celui des variables DB_*) : php artisan serve — la commande CMD du conteneur — ne transmet à ses workers HTTP qu'une liste blanche restreinte de variables d'environnement (ServeCommand::$passthroughVariables) ; les autres sont relues depuis .env à chaque requête. Sans correctif, les requêtes HTTP servies retomberaient sur 127.0.0.1 au lieu de mailpit. AppServiceProvider::boot() ajoute donc MAIL_MAILER, MAIL_HOST et MAIL_PORT à cette liste blanche, au même titre que les variables DB_* et AWS_ENDPOINT. Régression couverte par tests/Feature/Database/ServePassthroughTest.php.

Démarrer Mailpit

bash
docker compose up -d mailpit

Puis ouvrir l'interface : http://localhost:8025.

Stockage objet — MinIO & imgproxy

Le pipeline d'images (originaux sur S3, service via /img/ → imgproxy) est détaillé dans Stockage d'images. Côté environnement de développement :

  • minio — store S3-compatible local (image officielle minio/minio), ports 9000 (API) et 9001 (console web : http://localhost:9001), volume miniodata. Le root user/password réutiliseAWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY du .env (interpolation compose) — une seule paire d'identifiants.
  • minio-init — one-shot minio/mc qui crée le bucket (AWS_BUCKET, défaut roadtrip) idempotamment puis s'arrête : docker compose up suffit à obtenir un store fonctionnel.
  • imgproxy — port hôte 8081 (FORWARD_IMGPROXY_PORT), branché sur http://minio:9000, clés de signature IMGPROXY_KEY / IMGPROXY_SALT partagées avec le backend via le même .env.
  • Piège hôte vs conteneur : AWS_ENDPOINT vaut http://localhost:9000 dans .env (exécution sur l'hôte) mais http://minio:9000 dans le conteneur app (imposé par docker-compose.yml) — même mécanique et même liste blanche serve que DB_* / MAIL_* ci-dessus.
  • Le routage /img/ en dev relève du proxy Vite du dépôt frontend (comme /api) ; rien à configurer côté backend.
bash
docker compose up -d minio minio-init imgproxy

Comptes de test (seed)

php artisan db:seed peuple une base de développement représentative du modèle d'identité multi-casquettes (voir Identité & accès). Le DatabaseSeeder enchaîne RoleSeeder (les rôles globaux manager / employee), OrganizationSeeder, OrganizationCsvSeeder, SuperAdminSeeder, DemoScenarioSeeder, ActivationCodeSeeder (qui dote chaque reseller — synthétique, importé ou démo — de ses lots de codes), TagSeeder, ProviderSeeder puis LegacyActivitySeeder.

Le seed superpose trois couches :

  1. Données de référence réelles — importées d'extraits CSV gitignorés (OrganizationCsvSeeder, LegacyActivitySeeder) ;
  2. Volume synthétique — généré par factories (OrganizationSeeder, ProviderSeeder, ActivationCodeSeeder) ;
  3. Casting nommé — des comptes fixes écrits en PHP (SuperAdminSeeder, DemoScenarioSeeder).

La ligne de partage : une donnée dont la source de vérité vit hors du dépôt passe par un CSV gitignoré ; une donnée inventée est écrite en PHP et existe donc sur tous les checkouts (la CI n'a jamais les CSV — les tests de seed dérivent leurs comptages attendus des fichiers eux-mêmes, zéro en leur absence).

Mot de passe unique : tous les comptes seedés utilisent Pa55w0rd!.

Les e-mails sont déterministes (dérivés du slug d'organisation), donc reproductibles d'un reseed à l'autre — pratique pour des requêtes Bruno ou une connexion rapide. En revanche les noms d'organisation et d'utilisateur sont générés aléatoirement par les factories.

Membres d'organisation

Pour chacun des deux OrganizationType (reseller, region), 10 organisations sont créées (reseller-1reseller-10, region-1region-10), soit 20 organisations. Chaque organisation est dotée de :

  • 1 manager — rôle manager, statut compte active, e-mail manager@<type>-<n>.example.com ;
  • 2 employés — rôle employee, e-mails employee1@<type>-<n>.example.com et employee2@<type>-<n>.example.com. Leur statut de compte est active dans ~5 cas sur 6, sinon pending (pour exercer le refus de login des comptes non active).

soit 60 comptes membres. L'appartenance (Membership) est toujours active ; le rôle est scopé à l'organisation (tables spatie). Environ un membre sur trois est aussi marqué voyageur (is_traveller).

Exemples :

  • manager@reseller-1.example.com — manager de l'organisation reseller-1 ;
  • employee2@region-4.example.com — employé de l'organisation region-4.

Membres multi-casquettes

Une fois toutes les organisations dotées, 5 membres existants sont rattachés à une seconde organisation (rôle employee également, scopé à cette autre organisation). Ce sont eux qui exercent le modèle « plusieurs casquettes » : un même e-mail appartient alors à deux organisations avec un rôle par organisation. Le tirage étant aléatoire, ces comptes ne sont pas connus à l'avance — les repérer via une requête (User ayant plus d'une organisation) plutôt que par e-mail.

Organisations importées (CSV)

OrganizationCsvSeeder importe les organisations réelles depuis database/regions.csv (Nom,status,contact_email) et database/resellers.csv (Nom,commission,commission_type,status,contact_email). Comme pour le catalogue hérité : données métier réelles, volontairement hors git (règle *.csv de database/.gitignore) — obtenir les fichiers auprès de l'équipe. Le seeder s'ignore avec un avertissement quand un fichier est absent et OrganizationCsvSeederTest se marque skipped ; une valeur de status ou de commission_type inconnue fait échouer le seed bruyamment (::from()). La clé naturelle est (type, nom) : un re-seed est idempotent (par ex. php artisan db:seed --class=OrganizationCsvSeeder après un ajout de lignes).

Chaque organisation importée est dotée du même équipage fictif que les synthétiques (1 manager + 2 employés — le concern partagé StaffsOrganization). Les logins dérivent du slug du nom de l'organisation, jamais du contact_email réel (uniforme même sans e-mail de contact, et le seed ne peut jamais frapper un identifiant sur un domaine réel) : manager@graubunden-ferien.example.com, employee1@graubunden-ferien.example.com, … mot de passe Pa55w0rd!.

Super-admins et compte démo

SuperAdminSeeder crée 3 super-admins : admin1@example.com, admin2@example.com, admin3@example.com (active, reposant uniquement sur le bypass de Gate is_super_admin, voir Identité & accès). À ne pas confondre avec la commande idempotente app:ensure-super-admin, destinée, elle, à l'amorçage hors développement.

admin2 / admin3 restent l'axe pur du modèle : aucune organisation, pas voyageurs. admin1 est enrichi par DemoScenarioSeeder en compte démo omni-casquettes : voyageur, et membre de 4 organisations démo créées par le seeder lui-même (statut approved, admin1 seul membre, clé (type, nom) — donc re-seedable sans doublon) :

OrganisationTypeRôle d'admin1
Demo Région Lémanregionmanager
Demo Région Grisonsregionemployee
Demo Kiosque Gareresellermanager
Demo Boutique Lacreselleremployee

admin1 est aussi client de 3 codes d'activation préfixés DEMO émis par Demo Kiosque Gare : 2 achetés, 1 utilisé, durée 12 h — de quoi dérouler démos et requêtes Bruno sur un état connu d'avance.

Catalogue (prestataires & activités)

Trois seeders complémentaires peuplent le catalogue :

  • TagSeeder étend le registre de tags au-delà du socle seedé par les migrations (affaires à prendre, restrictions, suitabilities — les thèmes, vocabulaire hérité complet, ne sont pas touchés). Idempotent et non destructif (ON CONFLICT DO NOTHING sur (type, slug)) : il n'écrase jamais un libellé ou une icône édités par un super-admin.

  • ProviderSeeder génère par factories des prestataires fictifs avec leurs activités, pour exercer tout le cycle éditorial : brouillons et origines (imported / signed_up).

  • LegacyActivitySeeder importe database/activities.csv, un extrait réel de l'export client (l'ancien système confondait prestataire et activité dans un même enregistrement : chaque ligne CSV donne un prestataire approuvé, origin = imported, plus son activité et ses tags de thème). Les prestataires sont clés par legacy_pk, donc un re-seed est idempotent ; une étiquette de pays ou une valeur de vocabulaire inconnue fait échouer le seed bruyamment (match non exhaustif) pour signaler un nouvel export à cartographier. Les colonnes sans équivalent dans le nouveau schéma (liens destination, images, champs tour, doublons *_export…) sont ignorées. Le CSV étant une donnée client réelle, volontairement hors git (database/.gitignore), le seeder s'ignore avec un avertissement quand le fichier est absent, et son test de feature se marque skipped — obtenir le fichier auprès de l'équipe pour seeder le catalogue réel.

Contributors

No contributors

Changelog

No recent changes