Conventions de développement

Conventions de code applicables à tout le projet — frontières de modules, découpage Vue, i18n, API, sécurité, stockage et qualité.

Frontières de modules (serveur)

  • Chaque feature appartient à un module métier sous app/mypromo/server/modules/.
  • Importer depuis un autre module uniquement via app/mypromo/server/modules/<module>/index.ts.
  • Règles de couche : règles métier dans domain, orchestration dans application, adaptateurs externes dans infrastructure, handlers de routes dans api.
  • Les primitives partagées utilisées par plugins, middleware ou routes proviennent de app/mypromo/server/modules/shared/index.ts — jamais depuis des sous-chemins internes tels que app/mypromo/server/modules/shared/api ou app/mypromo/server/modules/shared/infrastructure/*.
  • Les usages cross-module des notifications passent par app/mypromo/server/modules/notifications/index.ts ; les chemins internes email/* restent privés au module.

Frontières du shell frontend

  • Ce dépôt héberge uniquement le produit applicatif (auth, onboarding, espaces de travail par rôle, admin, APIs).
  • Les pages marketing/vitrine (site public, landings, pages légales marketing) sont maintenues dans un projet et une stack séparés.
  • Maintenir les pages applicatives sur app/mypromo/app/layouts/default.vue (auth, onboarding, espaces de travail par rôle, admin).
  • Ne pas ajouter de nouvelles routes ou logiques de layout vitrine dans ce dépôt.
  • Les anciens chemins vitrine, si nécessaire, sont des redirections compatibilité vers /auth uniquement.
  • Pour les pages applicatives volumineuses, extraire les contrôles UI pilotés par données en composants dédiés (ex. sélecteurs de filtres gérant leur propre chargement API/état) afin de garder les pages en mode orchestration uniquement.

Découpage des fichiers Vue

Principe général

  • Les pages (app/mypromo/app/pages/*) n'orchestrent que : paramètres de route, chargement async, état de haut niveau, câblage entre blocs fonctionnels.
  • Extraire un composant réutilisable dès qu'un bloc de template apparaît en deux endroits, ou quand une page dépasse environ 300-400 LOC avec des responsabilités mélangées.
  • Découper par domaine métier (store, organization, ad-space, campaign) avant tout.
  • Un composant a une responsabilité dominante : carte d'affichage, bloc de filtre, étape d'éditeur, panneau récapitulatif, etc.
  • Déplacer la logique d'intégration lourde (cartes, polling API, upload, état synchronisé sur l'URL) dans des composables sous app/mypromo/app/composables/*.

Checklist d'extraction d'un composant enfant

Extraire dès qu'au moins une condition est vraie :

  • Balisage ou comportement répété.
  • Le bloc a son propre état local et sa propre validation.
  • Le bloc a plus d'un événement/sortie.
  • La lisibilité du parent en pâtit.

Responsabilité du composant parent :

  • Récupérer/sauvegarder les données.
  • États globaux de chargement/erreur.
  • Navigation et feedback de niveau page.

Responsabilité du composant enfant :

  • Présentation locale.
  • Normalisation locale des inputs.
  • Émission d'événements typés.

Contrats de composants

  • Définir des contrats typés explicites avec defineProps et des émissions typées.
  • Préférer les bindings v-model pour les contrôles réutilisables de type formulaire, avec des noms d'événements stables et des formes de valeurs prévisibles.
  • Éviter de passer des payloads backend bruts profondément dans l'arbre ; les mapper vers des view models adaptés à l'UI à la frontière de la feature.
  • Garder les listes statiques d'options près du composant propriétaire (options.ts) plutôt que dans des constantes de niveau page.

Niveaux de réutilisabilité

  • app/mypromo/app/components/<entity>/* : composants métier cross-feature (défaut recommandé pour les blocs réutilisables).
  • app/mypromo/app/components/<feature>/* : blocs de composition locaux à une feature, pas encore partagés.
  • app/mypromo/app/components/* (racine) : uniquement pour des blocs de construction largement réutilisables et agnostiques au domaine.

Anti-patterns à éviter

  • Fichiers de page volumineux mélangeant appels API, règles métier, règles de formulaire et rendu complet du template.
  • Composants enfants produisant des effets de bord parents cachés (navigation directe, mutation du store global sans contrat).
  • Variantes copier-coller du même bloc avec de légères différences de libellé (utiliser props/slots/options).
  • "God composables" non scopés agrégeant des responsabilités sans rapport.

Composants UI

Utiliser Nuxt UI 4 comme choix par défaut pour les primitives frontend et les interactions dès qu'un équivalent existe (UInput, UTextarea, USelect, USelectMenu, UButton, UCard, UAlert, etc.). Ne garder les éléments HTML natifs que pour des cas justifiés (comportement de composant manquant, contraintes d'accessibilité, besoin d'intégration bas niveau).

Pour les collections en admin/serveur, standardiser le rendu des listes avec <AkDataList> (de @aidalinfo/nuxt-ui-kit) piloté par useServerAdminList (recherche, pagination, synchronisation URL) sauf si une tâche exige une visualisation différente. UTable + TableTemplate restent utilisés dans les workspaces non-admin et les vues héritées où ils sont légitimement en place.

Pour les valeurs de référence détaillées (layout, en-têtes de page, couleurs, typographie, tables, boutons, notifications), voir le Guide UI.

Internationalisation (i18n)

  • Utiliser @nuxtjs/i18n comme source de vérité unique pour la localisation du texte applicatif.
  • Conserver les locales dans app/mypromo/i18n/locales/*.json et préférer des clés stables et namespacées (layout.*, auth.*, onboarding.*, etc.).
  • Garder la stratégie de routing no_prefix sauf décision produit explicite de localiser les URLs.
  • Dans app/mypromo/app/app.vue, lier la locale de UApp à useI18n().locale avec @nuxt/ui/locale pour aligner les libellés intégrés de Nuxt UI et les formats date/heure avec la langue active.
  • Pour les composants réutilisables, fournir des valeurs par défaut compatibles i18n (libellés/placeholders traduits par défaut) tout en autorisant des surcharges explicites via props.

API et erreurs

  • Utiliser les helpers partagés de app/mypromo/server/modules/shared/api pour les handlers de routes.
  • Pour les routes auth protégées, utiliser les wrappers RBAC de app/mypromo/server/modules/auth/api/middleware.ts.
  • Toujours retourner des enveloppes standardisées (ok, data/error, meta).
  • Utiliser des codes d'erreur stables (API_ERROR_CODES) et éviter les chaînes libres fragiles.

Tracing des requêtes et logs

  • Propager requestId du middleware vers tous les logs.
  • Émettre des logs JSON structurés avec timestamp, level, scope, message, requestId.
  • Ne jamais logger de secrets (clés, mots de passe, tokens, credentials complets).

Sécurité

  • Ne pas inclure de secrets d'application de repli dans les chemins de code (les secrets auth/session doivent être explicites dans l'environnement).
  • Protéger les endpoints auth et admin critiques avec des contrôles anti-abus (rate limiting a minima).
  • Préférer des flux de consentement utilisateur explicites pour les changements de membres d'organisation (invitation + acceptation, jamais d'attachement direct).

Géocodage

  • Limiter l'usage de Google Maps aux workflows de géo-référencement (onboarding, import de magasins, rafraîchissement admin du géoréférencement).
  • Ne jamais appeler le géocodage Google depuis les endpoints de recherche/affichage de carte ; utiliser le champ stores.latitude/stores.longitude persisté.

Stockage de fichiers

  • Centraliser les contextes d'upload, les contraintes MIME/taille et les conventions de dossier dans des helpers partagés (app/mypromo/server/modules/shared/domain/file-storage.ts, app/mypromo/server/modules/shared/application/file-storage.ts).
  • Éviter les chemins de stockage codés en dur par feature dans les handlers/services ; résoudre les chemins via les IDs de contexte et les segments de scope.
  • Limiter le service de fichiers publics aux contextes explicites ; les contextes privés nécessitent des endpoints authentifiés dédiés.
  • Contrat d'upload standard pour les APIs : accepter les items de payload files[] sérialisés (name, content en data URL base64, size, type, lastModified) générés depuis les sélections UFileUpload.
  • Orchestration serveur standard pour les services de features :
    1. Valider le payload domaine en premier.
    2. Persister les fichiers via storeFilesForContext avec des segments de scope propres à la feature.
    3. Stocker les URLs /api/files/... résultantes dans les entités métier (jamais de chemins absolus locaux).
    4. En mise à jour/suppression, supprimer les fichiers managés orphelins via deleteStoredFilesByUrls scopé par contexte + préfixe.
    5. Assurer un rollback de nettoyage en cas d'échec partiel (l'écriture DB échoue après l'upload).

Portes de qualité

Validation locale avant PR :

pnpm run check    # lint + typecheck + test:unit + test:integration
pnpm run build
pnpm run test:e2e # pour les flux UI/API

Le script check local de app/mypromo exécute lint + typecheck + test. Le script check à la racine du dépôt inclut en plus le vérificateur de format (format:check).

La règle de frontières de modules est vérifiée par app/mypromo/tests/unit/module-boundaries.spec.ts — doit rester verte.

La CI doit rester verte sur lint, typecheck, tests, build.

Commits et branches

  • Suivre les Conventional Commits.
  • Garder les commits focalisés par module/concern.
  • Préférer des PRs petites et relisibles avec un scope explicite.