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:
2026-09-20 18:21:24 +02:00
co-authored by Claude Opus 5
parent 4bf3dd96df
commit 063c5758a7
9 changed files with 17 additions and 15 deletions
+5 -3
View File
@@ -9,8 +9,10 @@
# 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
# et les liens du README en dépendent tous. Doit rester cohérent avec AUTH_URL.
APP_DOMAIN=horaires.loxi.ch
# Port publié en local (dev). En production, Traefik s'en charge : aucun port publié.
APP_DOMAIN=horaires.ita-ito.com
# 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_BIND=127.0.0.1
TZ=Europe/Zurich
@@ -34,7 +36,7 @@ TEST_DATABASE_URL=
#
# URI de redirection à déclarer dans Authentik (correspondance stricte) :
# <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
AUTH_SECRET=
BIN
View File
Binary file not shown.
+6 -6
View File
@@ -47,7 +47,7 @@ par deux chemins qui ne se croisent jamais.
│ 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/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` |
**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
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
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
AUTHENTIK_ISSUER # https://auth.loxi.ch/application/o/<SLUG>/ ← À CONFIRMER
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
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:3000/api/device/image/<hash>.bmp -o /tmp/screen.bmp
curl -s localhost:3010/api/display -H "Access-Token: <clé du seed>" | jq
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
# 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
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.
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
+1 -1
View File
@@ -29,7 +29,7 @@ export function publicBaseUrl(request?: Request): string {
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",
},
"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,
"shop_name": "ITA ITO",
"today": {
+1 -1
View File
@@ -9,7 +9,7 @@ const PAYLOAD: ScreenPayload = {
schema: 1,
generated_at: '2026-09-22T12:00:00+02:00',
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: {
date_fr: 'Mardi 22 septembre',
date_en: 'Tuesday 22 September',
+1 -1
View File
@@ -37,7 +37,7 @@ function input(overrides: Partial<Parameters<typeof buildScreenPayload>[0]> = {}
now: new Date('2026-09-22T10:00:00Z'),
ctx: context(),
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[],
...overrides,
};
+1 -1
View File
@@ -9,7 +9,7 @@
"scripts": {
"dev": "next dev --port 3010",
"build": "next build",
"start": "next start",
"start": "next start --port ${PORT:-3010}",
"lint": "eslint . --max-warnings 0",
"typecheck": "tsc --noEmit",
"test": "vitest run",
+1 -1
View File
@@ -61,7 +61,7 @@ function payload(): ScreenPayload {
now: new Date('2026-09-23T10:30:00Z'),
ctx: CONTEXT,
shopName: 'ITA ITO',
logoUrl: 'https://trmnl.loxi.ch/brand/logo-eink.png',
logoUrl: 'https://horaires.ita-ito.com/brand/logo-eink.png',
messages: [
{
textFr: 'Fermeture exceptionnelle jeudi matin',