@mostajs/wagon-engine 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,25 @@
3
3
  **Auteur** : Dr Hamid MADANI <drmdh@msn.com>
4
4
  Format : Keep a Changelog ; versions : SemVer.
5
5
 
6
+ ## [0.5.0] — 2026-10-28
7
+
8
+ ### Ajouté
9
+ - **`config.cadreWagon`** : le cadre des pages du moteur (tarifs, inscription, connexion, aide, à propos, compte,
10
+ déconnexion), par exemple `{ disposition: 'application', application: { retour: '/', langues: true } }`. Une greffe
11
+ qui vit dans la disposition « application » n'y renvoie plus la vitrine et son menu commun à tous les publics. Une
12
+ page de la charge qui choisit son cadre le garde. Essai T-ENG-13.
13
+
14
+ ### Corrigé
15
+ - **Le pied de page ne liste que les textes légaux servis** (`legaux`, `@mostajs/wagon-ui` ≥ 0.11) : un texte incomplet
16
+ n'était pas servi (0.4) mais restait lié, d'où un 404 sur chaque page. Essai T-ENG-12.
17
+
18
+ ## [0.4.1] — 2026-10-28
19
+
20
+ ### Corrigé
21
+ - **Le prix de l'inscription suit les moyens de paiement actifs** : la monnaie de chaque banque prépayée (Chargily :
22
+ DZD), puis l'euro si Stripe est actif. Une école payant en dinars voyait « 19,00 €/mois » sur « Créer un compte »
23
+ et « 8 000 DZD » sur les tarifs (Speaking, 28/10). Demande `@mostajs/wagon-ui` ≥ 0.10.1. Essai T-ENG-11.
24
+
6
25
  ## [0.4.0] — 2026-10-27
7
26
 
8
27
  ### Ajouté
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/wagon-engine",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Le wagon d'un SaaS, une fois pour toutes : comptes, connexion, paliers, quotas, paiement (Stripe, Chargily, SATIM), espace client, administration, pages légales, fr/en/ar. Une greffe n'apporte que wagon.config.mjs, charge/, legal/ et son .env ; la suite de conformité publiée juge la greffe.",
5
5
  "keywords": [
6
6
  "mostajs",
@@ -52,7 +52,7 @@
52
52
  "@mostajs/orm": "^2.12.1",
53
53
  "@mostajs/payment": "^2.6.0",
54
54
  "@mostajs/subscriptions-plan": "^0.9.0",
55
- "@mostajs/wagon-ui": "^0.10.0",
55
+ "@mostajs/wagon-ui": "^0.11.0",
56
56
  "better-sqlite3": "^12.11.1",
57
57
  "stripe": "^17.7.0"
58
58
  },
package/src/wagon.mjs CHANGED
@@ -347,6 +347,14 @@ export async function creerWagon({ config, charge, sgbd, baseUrl, stripe = null,
347
347
  return { html: complet ? mdToHtml(texte).html : '', complet };
348
348
  }
349
349
 
350
+ /** 0.5 — les textes légaux servis dans une langue (lus une fois : ils ne changent qu'au redéploiement). */
351
+ const LEGAUX = ['mentions-legales', 'cgu', 'cgv', 'confidentialite'];
352
+ const servis = new Map();
353
+ const legauxServis = (lang) => {
354
+ if (!servis.has(lang)) servis.set(lang, LEGAUX.filter((doc) => documentLegal(doc, lang).complet));
355
+ return servis.get(lang);
356
+ };
357
+
350
358
  async function vueEspace(compte) {
351
359
  const actif = await facturation.activePlan(String(compte.id));
352
360
  const quotas = {};
@@ -413,7 +421,9 @@ export async function creerWagon({ config, charge, sgbd, baseUrl, stripe = null,
413
421
  const qui = s ? await comptes.qui(String(s.user ?? s.userId ?? '')) : null;
414
422
  const estAdmin = !!(qui && await estAdministrateur(qui.utilisateur.courriel));
415
423
  // 0.5 — `chemin` : la page réelle (canonique, liens de langue) ; `actif` reste l'onglet (wagon-ui 0.5.1).
416
- const page = (o) => ui.page({ lang, connecte: !!qui, base: origine, chemin, ...o });
424
+ // 0.5 — le pied de page ne liste que les textes légaux SERVIS (un lien vers un texte incomplet menait à un 404) ;
425
+ // `config.cadreWagon` donne le cadre des pages du moteur (une page de la charge qui choisit le sien le garde).
426
+ const page = (o) => ui.page({ lang, connecte: !!qui, base: origine, chemin, legaux: legauxServis(lang), ...(config.cadreWagon ?? {}), ...o });
417
427
  const ouvrirSession = async (userId, vers = '/compte') => rediriger(vers, { 'set-cookie': cookieStrict('wagon_session', await sessions.ouvrir(userId), { maxAge: 30 * 86400, https }) });
418
428
 
419
429
  // 0.5 — la santé : le commit EN SERVICE (fichier REVISION posé par le déployeur) et l'heure de démarrage.
@@ -602,14 +612,23 @@ export async function creerWagon({ config, charge, sgbd, baseUrl, stripe = null,
602
612
  if (chemin === '/inscription') {
603
613
  const tous = await paliersEnBase();
604
614
  const palierDe = (slug) => tous.find((p) => p.slug === slug && p.active !== false) ?? tous.find((p) => p.slug === (config.planGratuit ?? 'gratuit'));
615
+ // 0.4.1 — le prix montré est celui des moyens ACTIFS : la monnaie de chaque banque prépayée, puis l'euro de Stripe
616
+ // (une école payant en dinars voyait l'euro de Stripe ici, et les dinars sur /tarifs).
617
+ const prixDe = (p) => {
618
+ if (!p) return null;
619
+ const pp = typeof p.prepaid === 'string' ? JSON.parse(p.prepaid || 'null') : p.prepaid;
620
+ const l = [...new Set(Object.values(banques))].filter((dv) => Number(pp?.[dv]?.month) > 0).map((dv) => ({ centimes: Number(pp[dv].month), devise: dv }));
621
+ if (stripe && Number(p.price) > 0) l.push({ centimes: Number(p.price), devise: 'EUR' });
622
+ return l.length ? l : null;
623
+ };
605
624
  const suite = suiteSure(url.searchParams.get('suite'));
606
625
  const action = avecSuite('/inscription', suite);
607
- if (methode === 'GET') return envoyer(200, page({ titre: t('inscription.titre'), contenu: ui.inscription({ lang, palier: palierDe(url.searchParams.get('plan')), methode: methodeConnexion, minMotDePasse: motsDePasse.policy.minLength, action }) }));
626
+ if (methode === 'GET') return envoyer(200, page({ titre: t('inscription.titre'), contenu: ui.inscription({ lang, palier: palierDe(url.searchParams.get('plan')), prix: prixDe(palierDe(url.searchParams.get('plan'))), methode: methodeConnexion, minMotDePasse: motsDePasse.policy.minLength, action }) }));
608
627
  if (!freinInscription.take(`ip:${ipClient(req)}`).ok) return envoyer(429, page({ titre: t('inscription.titre'), contenu: `<p class="msg">${esc((TEXTES_COMPTE[lang] ?? TEXTES_COMPTE.fr).trop)}</p>` }), { 'retry-after': '60' });
609
628
  const f = await formulaire();
610
629
  const palier = palierDe(f.plan);
611
630
  const courriel = String(f.courriel ?? '').trim().toLowerCase();
612
- const refus = (m) => envoyer(200, page({ titre: t('inscription.titre'), contenu: ui.inscription({ lang, palier, erreur: m, methode: methodeConnexion, minMotDePasse: motsDePasse.policy.minLength, action }) }));
631
+ const refus = (m) => envoyer(200, page({ titre: t('inscription.titre'), contenu: ui.inscription({ lang, palier, prix: prixDe(palier), erreur: m, methode: methodeConnexion, minMotDePasse: motsDePasse.policy.minLength, action }) }));
613
632
  if (!COURRIEL.test(courriel) || !String(f.nom ?? '').trim()) return refus(t('inscription.courrielAide'));
614
633
  if (await d.findOne(S.User, { email: courriel })) return refus(t('inscription.deja'));
615
634
  if (methodeConnexion === 'motdepasse') {