feat: normaliser les URL production par entité
Some checks failed
Validation applicative / TypeScript, tests et build (push) Failing after 1m35s

This commit is contained in:
Santinova Deployment
2026-10-24 17:06:48 +00:00
parent a7845f5b06
commit 9e097e585a
12 changed files with 37 additions and 36 deletions

View File

@@ -22,7 +22,7 @@ nano .env
### 3. Ajouter la variable APP_PUBLIC_URL ### 3. Ajouter la variable APP_PUBLIC_URL
Ajoutez cette ligne dans le fichier `.env` : Ajoutez cette ligne dans le fichier `.env` :
``` ```
APP_PUBLIC_URL=https://formations.itinova.org APP_PUBLIC_URL=https://formations-itinova.santinova-soft.org
``` ```
### 4. Redémarrer l'application ### 4. Redémarrer l'application
@@ -38,7 +38,7 @@ sudo systemctl restart formation-manager-itinova
1. Connectez-vous à l'interface formateur 1. Connectez-vous à l'interface formateur
2. Générez un nouveau QR code pour une séquence 2. Générez un nouveau QR code pour une séquence
3. Scannez le QR code avec votre téléphone 3. Scannez le QR code avec votre téléphone
4. Vérifiez que l'URL commence par `https://formations.itinova.org/emargement/...` 4. Vérifiez que l'URL commence par `https://formations-itinova.santinova-soft.org/emargement/...`
## Alternative : Utiliser DOMAIN_NAME ## Alternative : Utiliser DOMAIN_NAME
Si vous préférez, vous pouvez aussi utiliser : Si vous préférez, vous pouvez aussi utiliser :

View File

@@ -101,7 +101,7 @@ server {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade; proxy_cache_bypass $http_upgrade;
# Timeouts # Timeouts
proxy_connect_timeout 60s; proxy_connect_timeout 60s;
proxy_send_timeout 60s; proxy_send_timeout 60s;
@@ -170,12 +170,12 @@ Si tout se passe bien, vous verrez un message de succès indiquant que le certif
Testez que votre site est accessible en HTTPS : Testez que votre site est accessible en HTTPS :
```bash ```bash
curl -I https://formations.itinova.org curl -I https://formations-itinova.santinova-soft.org
``` ```
Vous devriez voir une réponse HTTP/2 200 OK. Vous devriez voir une réponse HTTP/2 200 OK.
Ouvrez votre navigateur et accédez à `https://formations.itinova.org`. Vous devriez voir : Ouvrez votre navigateur et accédez à `https://formations-itinova.santinova-soft.org`. Vous devriez voir :
- Un cadenas vert dans la barre d'adresse - Un cadenas vert dans la barre d'adresse
- Votre application fonctionnant normalement - Votre application fonctionnant normalement
- Aucun avertissement de sécurité - Aucun avertissement de sécurité
@@ -387,6 +387,6 @@ Pour aller plus loin, vous pouvez configurer un pare-feu (ufw), mettre en place
--- ---
**Auteur** : Manus AI **Auteur** : Manus AI
**Date** : 23 novembre 2025 **Date** : 23 novembre 2025
**Version** : 1.0 **Version** : 1.0

View File

@@ -3,8 +3,8 @@
"name": "Formation Manager", "name": "Formation Manager",
"category": "ITINOVA", "category": "ITINOVA",
"urls": { "urls": {
"recette": "https://formations.recette.santinova-soft.org", "recette": "https://formations-itinova.recette.santinova-soft.org",
"prod": "https://formations.santinova-soft.org" "prod": "https://formations-itinova.santinova-soft.org"
}, },
"containerName": "formation-manager-itinova", "containerName": "formation-manager-itinova",
"image": "images/formation-manager.png", "image": "images/formation-manager.png",

View File

@@ -163,7 +163,7 @@ export default function AdminEmailTemplates() {
.replace(/\{\{formateur\}\}/g, 'Jean Martin') .replace(/\{\{formateur\}\}/g, 'Jean Martin')
.replace(/\{\{couleurPrincipale\}\}/g, formData.couleurPrincipale) .replace(/\{\{couleurPrincipale\}\}/g, formData.couleurPrincipale)
.replace(/\{\{couleurSecondaire\}\}/g, formData.couleurSecondaire) .replace(/\{\{couleurSecondaire\}\}/g, formData.couleurSecondaire)
.replace(/\{\{lienReinitialisation\}\}/g, 'https://formations.itinova.org/reset-password/token123') .replace(/\{\{lienReinitialisation\}\}/g, 'https://formations-itinova.santinova-soft.org/reset-password/token123')
.replace(/\{\{datesHTML\}\}/g, '<div class="date-item"><strong>Date 1 :</strong> lundi 15 janvier 2025</div><div class="date-item"><strong>Date 2 :</strong> mardi 16 janvier 2025</div>'); .replace(/\{\{datesHTML\}\}/g, '<div class="date-item"><strong>Date 1 :</strong> lundi 15 janvier 2025</div><div class="date-item"><strong>Date 2 :</strong> mardi 16 janvier 2025</div>');
}; };
@@ -369,7 +369,7 @@ export default function AdminEmailTemplates() {
<div> <div>
<Label htmlFor="bodyContent">Corps du message</Label> <Label htmlFor="bodyContent">Corps du message</Label>
{/* Boutons de variables dynamiques */} {/* Boutons de variables dynamiques */}
<div className="flex flex-wrap gap-2 mb-2"> <div className="flex flex-wrap gap-2 mb-2">
<Button <Button

View File

@@ -14,14 +14,14 @@ import DashboardLayout from "@/components/DashboardLayout";
export default function AdminParametres() { export default function AdminParametres() {
const { data: parametres, isLoading, refetch } = trpc.parametres.get.useQuery(); const { data: parametres, isLoading, refetch } = trpc.parametres.get.useQuery();
const { data: historique, isLoading: isLoadingHistorique } = trpc.parametres.getHistorique.useQuery({ limit: 20 }); const { data: historique, isLoading: isLoadingHistorique } = trpc.parametres.getHistorique.useQuery({ limit: 20 });
const [urlPublique, setUrlPublique] = useState(""); const [urlPublique, setUrlPublique] = useState("");
const [delaiExpirationQR, setDelaiExpirationQR] = useState(30); const [delaiExpirationQR, setDelaiExpirationQR] = useState(30);
const [dureeValiditeToken, setDureeValiditeToken] = useState(24); const [dureeValiditeToken, setDureeValiditeToken] = useState(24);
const [notificationsActives, setNotificationsActives] = useState(true); const [notificationsActives, setNotificationsActives] = useState(true);
const [testUrlStatus, setTestUrlStatus] = useState<{ loading: boolean; result?: { accessible: boolean; message: string } }>({ loading: false }); const [testUrlStatus, setTestUrlStatus] = useState<{ loading: boolean; result?: { accessible: boolean; message: string } }>({ loading: false });
// Initialiser les valeurs quand les données sont chargées // Initialiser les valeurs quand les données sont chargées
useEffect(() => { useEffect(() => {
if (parametres) { if (parametres) {
@@ -60,7 +60,7 @@ export default function AdminParametres() {
const handleSubmit = (e: React.FormEvent) => { const handleSubmit = (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
// Validation de l'URL // Validation de l'URL
try { try {
new URL(urlPublique); new URL(urlPublique);
@@ -129,7 +129,7 @@ export default function AdminParametres() {
<Input <Input
id="urlPublique" id="urlPublique"
type="url" type="url"
placeholder="https://formations.itinova.org" placeholder="https://formations-itinova.santinova-soft.org"
value={urlPublique} value={urlPublique}
onChange={(e) => { onChange={(e) => {
setUrlPublique(e.target.value); setUrlPublique(e.target.value);
@@ -168,8 +168,8 @@ export default function AdminParametres() {
</Button> </Button>
</div> </div>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Cette URL est utilisée pour générer les QR codes d'émargement. Cette URL est utilisée pour générer les QR codes d'émargement.
Exemple : https://formations.itinova.org (sans slash à la fin) Exemple : https://formations-itinova.santinova-soft.org (sans slash à la fin)
</p> </p>
</div> </div>
@@ -225,8 +225,8 @@ export default function AdminParametres() {
</div> </div>
<div className="flex items-center gap-2 pt-4"> <div className="flex items-center gap-2 pt-4">
<Button <Button
type="submit" type="submit"
disabled={updateMutation.isPending} disabled={updateMutation.isPending}
className="gap-2" className="gap-2"
> >

View File

@@ -18,6 +18,7 @@ services:
env_file: env_file:
- .env - .env
environment: environment:
APP_PUBLIC_URL: https://formations-itinova.santinova-soft.org
NODE_ENV: production NODE_ENV: production
PORT: 3000 PORT: 3000
TRUST_PROXY: 1 TRUST_PROXY: 1
@@ -29,7 +30,7 @@ services:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.docker.network=web" - "traefik.docker.network=web"
- "traefik.http.routers.formations-prod.entrypoints=websecure" - "traefik.http.routers.formations-prod.entrypoints=websecure"
- "traefik.http.routers.formations-prod.rule=Host(`formations.santinova-soft.org`)" - "traefik.http.routers.formations-prod.rule=Host(`formations.santinova-soft.org`) || Host(`formations-itinova.santinova-soft.org`)"
- "traefik.http.routers.formations-prod.tls=true" - "traefik.http.routers.formations-prod.tls=true"
- "traefik.http.routers.formations-prod.tls.certresolver=letsencrypt" - "traefik.http.routers.formations-prod.tls.certresolver=letsencrypt"
- "traefik.http.routers.formations-prod.service=formations-prod-svc" - "traefik.http.routers.formations-prod.service=formations-prod-svc"

View File

@@ -571,7 +571,7 @@ export type InsertLogNotification = typeof logsNotifications.$inferInsert;
export const parametres = mysqlTable("parametres", { export const parametres = mysqlTable("parametres", {
id: int("id").autoincrement().primaryKey(), id: int("id").autoincrement().primaryKey(),
/** URL publique de l'application pour les QR codes d'émargement */ /** URL publique de l'application pour les QR codes d'émargement */
urlPublique: varchar("urlPublique", { length: 500 }).notNull().default("https://formations.itinova.org"), urlPublique: varchar("urlPublique", { length: 500 }).notNull().default("https://formations-itinova.santinova-soft.org"),
/** Délai d'expiration des QR codes en minutes (par défaut 30 minutes) */ /** Délai d'expiration des QR codes en minutes (par défaut 30 minutes) */
delaiExpirationQR: int("delaiExpirationQR").default(30).notNull(), delaiExpirationQR: int("delaiExpirationQR").default(30).notNull(),
/** Durée de validité des tokens d'émargement en heures (par défaut 24 heures) */ /** Durée de validité des tokens d'émargement en heures (par défaut 24 heures) */

View File

@@ -237,7 +237,7 @@ else
fi fi
execute_sql "Ajout de la colonne urlPublique" \ execute_sql "Ajout de la colonne urlPublique" \
"ALTER TABLE parametres ADD COLUMN urlPublique VARCHAR(500) NOT NULL DEFAULT 'https://formations.itinova.org' AFTER id;" "ALTER TABLE parametres ADD COLUMN urlPublique VARCHAR(500) NOT NULL DEFAULT 'https://formations-itinova.santinova-soft.org' AFTER id;"
execute_sql "Ajout de la colonne delaiExpirationQR" \ execute_sql "Ajout de la colonne delaiExpirationQR" \
"ALTER TABLE parametres ADD COLUMN delaiExpirationQR INT NOT NULL DEFAULT 30 AFTER urlPublique;" "ALTER TABLE parametres ADD COLUMN delaiExpirationQR INT NOT NULL DEFAULT 30 AFTER urlPublique;"

View File

@@ -20,7 +20,7 @@ if [ ! -f "$ENV_FILE" ]; then
fi fi
# Demander l'URL publique # Demander l'URL publique
echo "Entrez l'URL publique de votre application (ex: https://formations.itinova.org) :" echo "Entrez l'URL publique de votre application (ex: https://formations-itinova.santinova-soft.org) :"
read PUBLIC_URL read PUBLIC_URL
# Vérifier que l'URL commence par http:// ou https:// # Vérifier que l'URL commence par http:// ou https://

View File

@@ -17,7 +17,7 @@ describe("modèle d’e-mail de réinitialisation", () => {
}); });
it("substitue le lien de réinitialisation dans le contenu du modèle", () => { it("substitue le lien de réinitialisation dans le contenu du modèle", () => {
const link = "https://formations.recette.santinova-soft.org/reset-password?token=abc123"; const link = "https://formations-itinova.recette.santinova-soft.org/reset-password?token=abc123";
const result = replaceEmailVariables( const result = replaceEmailVariables(
'<a href="{{lienReinitialisation}}">Réinitialiser</a>', '<a href="{{lienReinitialisation}}">Réinitialiser</a>',
{ lienReinitialisation: link }, { lienReinitialisation: link },
@@ -38,7 +38,7 @@ describe("modèle d’e-mail de réinitialisation", () => {
}); });
const { generateEmailFromTemplate } = await import("../emailTemplateGenerator"); const { generateEmailFromTemplate } = await import("../emailTemplateGenerator");
const resetLink = "https://formations.recette.santinova-soft.org/reset-password?token=abc123"; const resetLink = "https://formations-itinova.recette.santinova-soft.org/reset-password?token=abc123";
const html = await generateEmailFromTemplate( const html = await generateEmailFromTemplate(
"reset_password", "reset_password",
`<a href="${resetLink}">Réinitialiser</a>`, `<a href="${resetLink}">Réinitialiser</a>`,

View File

@@ -11,17 +11,17 @@ export async function getParametres() {
if (!db) throw new Error("Database not available"); if (!db) throw new Error("Database not available");
const result = await db.select().from(parametres).limit(1); const result = await db.select().from(parametres).limit(1);
if (result.length === 0) { if (result.length === 0) {
// Créer les paramètres par défaut // Créer les paramètres par défaut
await db.insert(parametres).values({ await db.insert(parametres).values({
urlPublique: "https://formations.itinova.org", urlPublique: "https://formations-itinova.santinova-soft.org",
}); });
const newResult = await db.select().from(parametres).limit(1); const newResult = await db.select().from(parametres).limit(1);
return newResult[0]; return newResult[0];
} }
return result[0]; return result[0];
} }
@@ -44,7 +44,7 @@ export async function updateParametres(
if (!db) throw new Error("Database not available"); if (!db) throw new Error("Database not available");
const params = await getParametres(); const params = await getParametres();
// Enregistrer l'historique pour chaque champ modifié // Enregistrer l'historique pour chaque champ modifié
for (const [key, value] of Object.entries(updates)) { for (const [key, value] of Object.entries(updates)) {
const oldValue = params[key as keyof typeof params]; const oldValue = params[key as keyof typeof params];
@@ -58,12 +58,12 @@ export async function updateParametres(
}); });
} }
} }
// Mettre à jour les paramètres // Mettre à jour les paramètres
await db.update(parametres) await db.update(parametres)
.set(updates) .set(updates)
.where(eq(parametres.id, params.id)); .where(eq(parametres.id, params.id));
return getParametres(); return getParametres();
} }
@@ -113,8 +113,8 @@ export async function testerUrl(url: string): Promise<{ accessible: boolean; mes
return { return {
accessible: response.ok, accessible: response.ok,
message: response.ok message: response.ok
? "L'URL est accessible" ? "L'URL est accessible"
: `L'URL a retourné une erreur HTTP ${response.status}`, : `L'URL a retourné une erreur HTTP ${response.status}`,
statusCode: response.status statusCode: response.status
}; };
@@ -125,7 +125,7 @@ export async function testerUrl(url: string): Promise<{ accessible: boolean; mes
message: "Format d'URL invalide" message: "Format d'URL invalide"
}; };
} }
return { return {
accessible: false, accessible: false,
message: `Erreur lors du test: ${error instanceof Error ? error.message : 'Erreur inconnue'}` message: `Erreur lors du test: ${error instanceof Error ? error.message : 'Erreur inconnue'}`

View File

@@ -689,7 +689,7 @@
- [x] Créer la page AdminParametres - [x] Créer la page AdminParametres
- [x] Ajouter le lien dans le menu après "Configuration SMTP" - [x] Ajouter le lien dans le menu après "Configuration SMTP"
- [x] Modifier qrCodeGenerator pour utiliser l'URL de la BDD - [x] Modifier qrCodeGenerator pour utiliser l'URL de la BDD
- [x] Définir l'URL par défaut : https://formations.itinova.org - [x] Définir l'URL par défaut : https://formations-itinova.santinova-soft.org
- [x] Ajouter DashboardLayout à la page AdminParametres - [x] Ajouter DashboardLayout à la page AdminParametres
- [x] Ajouter DashboardLayout à la page FormateurEmargement - [x] Ajouter DashboardLayout à la page FormateurEmargement