chore: settle the public domain and move off port 3000
The application is published at horaires.ita-ito.com. One origin serves both the panel and the browser, so the OIDC callback, the image URL the device is handed and the documentation all follow from this single name. Port 3000 is already taken by the facture_ocr stack on the development machine, so the app listens on 3010 there and the reason is written next to the setting rather than left to be rediscovered. The fixtures and the frozen payload snapshot move to the real domain too: a contract snapshot carrying a hostname that never existed is a small puzzle left for whoever reads it next. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012cSY9pVhZmJUKNN7wf1Myd
This commit is contained in:
+5
-3
@@ -9,8 +9,10 @@
|
|||||||
# Nom de domaine public de l'app sur le VPS. Un seul domaine sert à la fois
|
# Nom de domaine public de l'app sur le VPS. Un seul domaine sert à la fois
|
||||||
# l'écran et le navigateur : le callback OIDC, l'URL d'image envoyée au panneau
|
# l'écran et le navigateur : le callback OIDC, l'URL d'image envoyée au panneau
|
||||||
# et les liens du README en dépendent tous. Doit rester cohérent avec AUTH_URL.
|
# et les liens du README en dépendent tous. Doit rester cohérent avec AUTH_URL.
|
||||||
APP_DOMAIN=horaires.loxi.ch
|
APP_DOMAIN=horaires.ita-ito.com
|
||||||
# Port publié en local (dev). En production, Traefik s'en charge : aucun port publié.
|
# Port publié sur l'hôte. 3000 est déjà pris par la stack facture_ocr sur la
|
||||||
|
# machine de développement, d'où 3010 ; en production Traefik s'en charge et
|
||||||
|
# aucun port n'est publié.
|
||||||
APP_PORT=3010
|
APP_PORT=3010
|
||||||
APP_BIND=127.0.0.1
|
APP_BIND=127.0.0.1
|
||||||
TZ=Europe/Zurich
|
TZ=Europe/Zurich
|
||||||
@@ -34,7 +36,7 @@ TEST_DATABASE_URL=
|
|||||||
#
|
#
|
||||||
# URI de redirection à déclarer dans Authentik (correspondance stricte) :
|
# URI de redirection à déclarer dans Authentik (correspondance stricte) :
|
||||||
# <AUTH_URL>/api/auth/callback/authentik
|
# <AUTH_URL>/api/auth/callback/authentik
|
||||||
AUTH_URL=https://horaires.loxi.ch
|
AUTH_URL=https://horaires.ita-ito.com
|
||||||
# Générer avec : openssl rand -base64 33
|
# Générer avec : openssl rand -base64 33
|
||||||
AUTH_SECRET=
|
AUTH_SECRET=
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ par deux chemins qui ne se croisent jamais.
|
|||||||
│ GET /api/device/image/<hash>.bmp ← non devinable, immuable, cacheable
|
│ GET /api/device/image/<hash>.bmp ← non devinable, immuable, cacheable
|
||||||
▼
|
▼
|
||||||
┌─────────────────────────────────────────────────────┐
|
┌─────────────────────────────────────────────────────┐
|
||||||
│ Next.js 16 (App Router, TS strict) trmnl.loxi.ch │
|
│ Next.js 16 (App Router, TS strict) horaires.ita-ito.com │
|
||||||
│ │
|
│ │
|
||||||
│ lib/schedule/resolver.ts ← pur, sans I/O, 100 % testé
|
│ lib/schedule/resolver.ts ← pur, sans I/O, 100 % testé
|
||||||
│ lib/screen/viewmodel.ts ← Prisma → ScreenPayload (schema 1)
|
│ lib/screen/viewmodel.ts ← Prisma → ScreenPayload (schema 1)
|
||||||
@@ -250,7 +250,7 @@ e2e/*.spec.ts ← Playwright
|
|||||||
| `GET /api/device/image/<hash>.bmp` | — | l'image, `Cache-Control: immutable` |
|
| `GET /api/device/image/<hash>.bmp` | — | l'image, `Cache-Control: immutable` |
|
||||||
|
|
||||||
**Appairage** (README, pas de reflash) : maintenir le bouton ~5 s → portail captif → Wi-Fi →
|
**Appairage** (README, pas de reflash) : maintenir le bouton ~5 s → portail captif → Wi-Fi →
|
||||||
*Advanced > Custom Server > Yes* → `https://trmnl.loxi.ch` **sans slash final**.
|
*Advanced > Custom Server > Yes* → `https://horaires.ita-ito.com` **sans slash final**.
|
||||||
|
|
||||||
`refresh_rate` adaptatif, calculé à chaque `/api/display` : `refreshRateOpenSec` (600) si la
|
`refresh_rate` adaptatif, calculé à chaque `/api/display` : `refreshRateOpenSec` (600) si la
|
||||||
boutique est ouverte ou ouvre dans l'heure, `refreshRateClosedSec` (7200) sinon, et raccourci
|
boutique est ouverte ou ouvre dans l'heure, `refreshRateClosedSec` (7200) sinon, et raccourci
|
||||||
@@ -314,7 +314,7 @@ change pas, badge + bouton « Réessayer ».
|
|||||||
|
|
||||||
```
|
```
|
||||||
DATABASE_URL POSTGRES_USER / PASSWORD / DB
|
DATABASE_URL POSTGRES_USER / PASSWORD / DB
|
||||||
APP_DOMAIN # ex. trmnl.loxi.ch — callback OIDC + image_url ← À CONFIRMER
|
APP_DOMAIN # ex. horaires.ita-ito.com — callback OIDC + image_url ← À CONFIRMER
|
||||||
NEXTAUTH_URL NEXTAUTH_SECRET
|
NEXTAUTH_URL NEXTAUTH_SECRET
|
||||||
AUTHENTIK_ISSUER # https://auth.loxi.ch/application/o/<SLUG>/ ← À CONFIRMER
|
AUTHENTIK_ISSUER # https://auth.loxi.ch/application/o/<SLUG>/ ← À CONFIRMER
|
||||||
AUTHENTIK_CLIENT_ID AUTHENTIK_CLIENT_SECRET
|
AUTHENTIK_CLIENT_ID AUTHENTIK_CLIENT_SECRET
|
||||||
@@ -412,8 +412,8 @@ npm run lint && npm run typecheck && npm run test -- --coverage
|
|||||||
|
|
||||||
# 3. Contrat d'affichage, sans appareil
|
# 3. Contrat d'affichage, sans appareil
|
||||||
npm run screen:preview # → aperçu 800×480 dans le navigateur
|
npm run screen:preview # → aperçu 800×480 dans le navigateur
|
||||||
curl -s localhost:3000/api/display -H "Access-Token: <clé du seed>" | jq
|
curl -s localhost:3010/api/display -H "Access-Token: <clé du seed>" | jq
|
||||||
curl -s localhost:3000/api/device/image/<hash>.bmp -o /tmp/screen.bmp
|
curl -s localhost:3010/api/device/image/<hash>.bmp -o /tmp/screen.bmp
|
||||||
file /tmp/screen.bmp # attendu : PC bitmap, 800 x 480 x 1
|
file /tmp/screen.bmp # attendu : PC bitmap, 800 x 480 x 1
|
||||||
|
|
||||||
# 4. Non-régression horaires (le test qui compte)
|
# 4. Non-régression horaires (le test qui compte)
|
||||||
@@ -431,7 +431,7 @@ du jour, une modification faite depuis un téléphone apparaît au réveil suiva
|
|||||||
|
|
||||||
## Points à confirmer
|
## Points à confirmer
|
||||||
|
|
||||||
1. **Domaine public** de l'app sur le VPS (`trmnl.loxi.ch` ?) — nécessaire au callback OIDC
|
1. **Domaine public** de l'app sur le VPS (`horaires.ita-ito.com` ?) — nécessaire au callback OIDC
|
||||||
et à l'`image_url` servie à l'appareil.
|
et à l'`image_url` servie à l'appareil.
|
||||||
2. **Slug de l'application Authentik** et **nom du groupe admin**.
|
2. **Slug de l'application Authentik** et **nom du groupe admin**.
|
||||||
3. **Clé API `llk_…`** pour `api.loxi.ch`, et confirmation que l'instance est bien joignable
|
3. **Clé API `llk_…`** pour `api.loxi.ch`, et confirmation que l'instance est bien joignable
|
||||||
|
|||||||
+1
-1
@@ -29,7 +29,7 @@ export function publicBaseUrl(request?: Request): string {
|
|||||||
return domain.startsWith('http') ? domain.replace(/\/$/, '') : `https://${domain}`;
|
return domain.startsWith('http') ? domain.replace(/\/$/, '') : `https://${domain}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'http://localhost:3000';
|
return 'http://localhost:3010';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ exports[`buildScreenPayload > matches the frozen payload contract 1`] = `
|
|||||||
"text_fr": "Fermeture exceptionnelle jeudi après-midi",
|
"text_fr": "Fermeture exceptionnelle jeudi après-midi",
|
||||||
},
|
},
|
||||||
"generated_at": "2026-09-22T12:00:00+02:00",
|
"generated_at": "2026-09-22T12:00:00+02:00",
|
||||||
"logo_url": "https://trmnl.loxi.ch/brand/logo-eink.png",
|
"logo_url": "https://horaires.ita-ito.com/brand/logo-eink.png",
|
||||||
"schema": 1,
|
"schema": 1,
|
||||||
"shop_name": "ITA ITO",
|
"shop_name": "ITA ITO",
|
||||||
"today": {
|
"today": {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const PAYLOAD: ScreenPayload = {
|
|||||||
schema: 1,
|
schema: 1,
|
||||||
generated_at: '2026-09-22T12:00:00+02:00',
|
generated_at: '2026-09-22T12:00:00+02:00',
|
||||||
shop_name: 'ITA ITO',
|
shop_name: 'ITA ITO',
|
||||||
logo_url: 'https://trmnl.loxi.ch/brand/logo-eink.png',
|
logo_url: 'https://horaires.ita-ito.com/brand/logo-eink.png',
|
||||||
today: {
|
today: {
|
||||||
date_fr: 'Mardi 22 septembre',
|
date_fr: 'Mardi 22 septembre',
|
||||||
date_en: 'Tuesday 22 September',
|
date_en: 'Tuesday 22 September',
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ function input(overrides: Partial<Parameters<typeof buildScreenPayload>[0]> = {}
|
|||||||
now: new Date('2026-09-22T10:00:00Z'),
|
now: new Date('2026-09-22T10:00:00Z'),
|
||||||
ctx: context(),
|
ctx: context(),
|
||||||
shopName: 'ITA ITO',
|
shopName: 'ITA ITO',
|
||||||
logoUrl: 'https://trmnl.loxi.ch/brand/logo-eink.png',
|
logoUrl: 'https://horaires.ita-ito.com/brand/logo-eink.png',
|
||||||
messages: [] as MessageCandidate[],
|
messages: [] as MessageCandidate[],
|
||||||
...overrides,
|
...overrides,
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --port 3010",
|
"dev": "next dev --port 3010",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start --port ${PORT:-3010}",
|
||||||
"lint": "eslint . --max-warnings 0",
|
"lint": "eslint . --max-warnings 0",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
|
|||||||
+1
-1
@@ -61,7 +61,7 @@ function payload(): ScreenPayload {
|
|||||||
now: new Date('2026-09-23T10:30:00Z'),
|
now: new Date('2026-09-23T10:30:00Z'),
|
||||||
ctx: CONTEXT,
|
ctx: CONTEXT,
|
||||||
shopName: 'ITA ITO',
|
shopName: 'ITA ITO',
|
||||||
logoUrl: 'https://trmnl.loxi.ch/brand/logo-eink.png',
|
logoUrl: 'https://horaires.ita-ito.com/brand/logo-eink.png',
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
textFr: 'Fermeture exceptionnelle jeudi matin',
|
textFr: 'Fermeture exceptionnelle jeudi matin',
|
||||||
|
|||||||
Reference in New Issue
Block a user