| .github/workflows | ||
| public | ||
| src | ||
| test | ||
| .env.example | ||
| .gitignore | ||
| docker-compose.dev.yml | ||
| docker-compose.yml | ||
| Dockerfile | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
Octane — outil de gestion du groupe
Application interne pour le groupe : répertoire de morceaux travaillés (avec liens de tutos par instrument), suggestions de nouveaux morceaux avec vote nominatif, setlist du prochain concert (avec rappel) et historique des concerts passés.
Stack 100% JavaScript : backend Node.js/Express servant des pages HTML/CSS/JS vanilla (pas de framework front, pas de build step) + API REST, PostgreSQL, authentification via OpenID Connect contre une instance Authentik existante.
Architecture
flowchart LR
subgraph Client["Navigateur"]
UI["HTML / CSS / JS vanilla"]
end
subgraph App["Conteneur app (Node.js / Express)"]
Static["Fichiers statiques (public/)"]
API["API REST /api/*"]
Auth["/auth/login, /auth/callback, /auth/logout"]
end
subgraph Infra["Infrastructure existante / conteneurs"]
DB[(PostgreSQL)]
Authentik["Authentik (OIDC)"]
end
UI -- "fetch()" --> API
UI -- "redirection navigateur" --> Auth
Auth -- "OIDC discovery + auth code + PKCE" --> Authentik
API --> DB
Auth -- "session (connect-pg-simple)" --> DB
Modèle de données
erDiagram
USERS ||--o{ SONGS : "ajoute"
USERS ||--o{ SUGGESTIONS : "propose"
USERS ||--o{ SUGGESTION_VOTES : "vote"
USERS ||--o{ SETLISTS : "cree"
SONGS ||--o{ SONG_TUTORIALS : "a des tutos"
INSTRUMENTS ||--o{ SONG_TUTORIALS : "concerne"
SUGGESTIONS ||--o{ SUGGESTION_VOTES : "recoit"
SUGGESTIONS }o--o| SONGS : "promue en"
SETLISTS ||--o{ SETLIST_SONGS : "contient"
SONGS ||--o{ SETLIST_SONGS : "figure dans"
USERS {
int id PK
text authentik_sub UK
text name
text email
bool is_admin
}
SONGS {
int id PK
text title
text artist
text notes
}
SONG_TUTORIALS {
int id PK
int song_id FK
int instrument_id FK
text url
text label
}
SUGGESTIONS {
int id PK
text title
text youtube_url
text status
int promoted_song_id FK
}
SUGGESTION_VOTES {
int id PK
int suggestion_id FK
int user_id FK
text vote
text comment
}
SETLISTS {
int id PK
text name
text venue
date concert_date
}
SETLIST_SONGS {
int id PK
int setlist_id FK
int song_id FK
int position
text note
bool is_encore
}
Fonctionnalités
| Page | Accès | Description |
|---|---|---|
/index.html |
Tous (lecture), admin (écriture) | Répertoire des morceaux travaillés, liens de tutos par morceau et par instrument |
/suggestions.html |
Tous | Proposer un morceau (avec lien YouTube embarqué), voter approuver/rejeter avec commentaire, attribué nominativement |
/setlist.html |
Tous (lecture), admin (écriture) | Setlist du prochain concert : ordre des morceaux, notes, section rappel |
/history.html, /history-detail.html |
Tous (lecture seule) | Historique des setlists des concerts passés |
Le mode par défaut est la consultation ; seule la page Suggestions est interactive (chaque vote est attribué à la personne connectée).
Rôles
- Membre : consulte tout, propose des suggestions, vote/commente.
- Admin : en plus, gère le répertoire, les tutos, promeut une suggestion approuvée en morceau du répertoire, crée/édite les setlists.
Le rôle admin est déterminé par un claim groups renvoyé par Authentik (voir configuration ci-dessous), recalculé à chaque connexion — Authentik reste la seule source de vérité des rôles.
Prérequis
- Docker + Docker Compose
- Une instance Authentik déjà en place, avec un réseau Docker accessible depuis ce projet
Configuration Authentik
- Créer un Provider OAuth2/OIDC dans Authentik, avec comme redirect URI la valeur que vous mettrez dans
OIDC_REDIRECT_URI(ex:https://octane.example.com/auth/callback). - Créer une Application Authentik pointant vers ce provider.
- S'assurer qu'un scope mapping expose un claim
groupsdans l'ID token (Authentik a un mappinggroupsintégré dans les versions récentes, sinon créer un mapping personnalisé renvoyantrequest.user.ak_groups.all()). - Créer un groupe Authentik (ex:
octane-admins) et y ajouter les membres qui doivent être admins de l'application. - Noter le Client ID / Client Secret du provider.
Installation
cp .env.example .env
Remplir .env :
| Variable | Description |
|---|---|
DATABASE_URL |
Chaîne de connexion Postgres (déjà cohérente avec le service postgres du compose) |
POSTGRES_PASSWORD |
Mot de passe du service Postgres |
SESSION_SECRET |
Chaîne aléatoire longue pour signer les cookies de session |
AUTHENTIK_ISSUER_URL |
URL d'issuer OIDC de l'application Authentik (ex: https://auth.example.com/application/o/octane-website/) |
OIDC_CLIENT_ID / OIDC_CLIENT_SECRET |
Identifiants du provider Authentik |
OIDC_REDIRECT_URI |
URL publique de callback, doit correspondre à celle configurée dans Authentik |
ADMIN_GROUP_NAME |
Nom du groupe Authentik dont les membres deviennent admins |
AUTHENTIK_NETWORK_NAME |
Nom du réseau Docker de votre stack Authentik existante (vérifier avec docker network ls) |
APP_PORT |
Port exposé sur l'hôte (défaut 3000) |
Puis démarrer :
docker compose pull
docker compose up -d
docker-compose.yml référence l'image publiée par la CI (ghcr.io/nfonteyne/octane-website:latest, voir CI/CD ci-dessous) plutôt que de la construire localement — c'est ce fichier que vous utilisez sur votre serveur.
Les migrations SQL (src/db/migrations/*.sql) sont exécutées automatiquement au démarrage du conteneur app, de façon idempotente (une table schema_migrations garde la trace des fichiers déjà appliqués).
Tester en local sans Authentik (ex: dans WSL)
Pas besoin d'avoir Authentik pour essayer l'application en premier lieu. Un mode DEV_BYPASS_AUTH remplace la redirection OIDC par un simple formulaire "choisissez un nom" — à n'utiliser qu'en local, jamais en production.
cp .env.example .env
Dans .env, mettre :
DEV_BYPASS_AUTH=true
DATABASE_URL=postgres://octane:changeme@postgres:5432/octane
POSTGRES_PASSWORD=changeme
SESSION_SECRET=une-longue-chaine-aleatoire
(Les variables AUTHENTIK_* / OIDC_* peuvent rester vides tant que DEV_BYPASS_AUTH=true.)
Puis, avec Docker Compose (fichier séparé docker-compose.dev.yml, sans dépendance au réseau Authentik) :
docker compose -f docker-compose.dev.yml up --build
Ou sans Docker du tout, avec un Postgres local :
npm install
# démarrer un Postgres local, renseigner DATABASE_URL dans .env
npm run migrate
npm start
Ouvrir http://localhost:3000 : vous serez redirigé vers /auth/login, qui affiche un formulaire pour choisir un nom (et cocher "Compte admin" si besoin) au lieu de passer par Authentik. Chaque nom saisi crée un utilisateur distinct et persistant en base — pratique pour tester le vote sur les suggestions avec plusieurs "personnes" (ouvrez un autre navigateur ou une fenêtre de navigation privée pour vous connecter sous un second nom).
Une fois satisfait, repassez DEV_BYPASS_AUTH=false et configurez les variables AUTHENTIK_*/OIDC_* avant de déployer avec docker-compose.yml (celui avec le réseau Authentik).
CI/CD et mises à jour
Le workflow .github/workflows/ci.yml se déclenche uniquement sur push vers main :
flowchart LR
Push["push sur main"] --> Test["Job test\nnpm ci + npm test"]
Test -- succès --> Build["Job build-and-push\ndocker build (app seule)"]
Build --> Push2["push vers ghcr.io\n:latest"]
Push2 -. "détecte le nouveau digest" .-> Watchtower["Watchtower (sur votre serveur)"]
Watchtower --> Redeploy["redéploie le conteneur app"]
- Job
test: installe les dépendances et lancenpm test(tests unitaires avec le test runner natif de Node —node --test, aucune dépendance de test supplémentaire). Actuellement couvre la validation des liens YouTube/Spotify (test/*.test.js). - Job
build-and-push(uniquement si les tests passent) : construit uniquement l'image de l'app (leDockerfilene contient que Node/Express, jamais Postgres) et la publie surghcr.io/nfonteyne/octane-website:latest.
Sur votre serveur, docker-compose.yml référence cette image directement (image: ghcr.io/nfonteyne/octane-website:latest) au lieu de la construire — Watchtower peut donc la surveiller et la mettre à jour automatiquement dès qu'un nouveau push sur main produit une nouvelle image.
Si le repo GitHub est privé, le package ghcr.io publié le sera aussi : sur le serveur, faites une fois :
echo "$GITHUB_TOKEN" | docker login ghcr.io -u nfonteyne --password-stdin
avec un token GitHub (classic PAT ou fine-grained) ayant le scope read:packages.
Pour lancer les tests en local :
npm test
Sauvegarde de la base de données
Aucune sauvegarde automatique n'est intégrée à l'application — c'est volontaire, pour que vous gardiez la main sur votre solution de backup externe. Les données Postgres vivent entièrement dans le volume Docker nommé pgdata (déclaré dans docker-compose.yml, monté sur /var/lib/postgresql/data du service postgres).
Repérer le nom réel du volume (préfixé par le nom du projet Compose) :
docker volume ls | grep pgdata
docker volume inspect <nom_du_volume> # donne le Mountpoint sur le disque de l'hôte
Deux façons de sauvegarder depuis l'extérieur :
- Backup logique (
pg_dump), recommandé, portable entre versions de Postgres :docker compose exec postgres pg_dump -U octane -d octane -F c -f /tmp/octane.dump docker compose cp postgres:/tmp/octane.dump ./octane_$(date +%Y%m%d).dump - Backup brut du volume, via le
Mountpointrenvoyé pardocker volume inspect, ou avec un conteneur utilitaire :docker run --rm -v <nom_du_volume>:/data -v "$(pwd)/backups":/backup alpine \ tar czf /backup/pgdata_$(date +%Y%m%d).tar.gz -C /data .
Branchez l'une de ces commandes sur votre outil de backup externe habituel (cron, Veeam, Borg, etc.).
Structure du projet
octane-website/
├── .github/workflows/ci.yml # tests + build/push de l'image Docker sur push main
├── Dockerfile, docker-compose.yml, docker-compose.dev.yml
├── test/ # tests unitaires (node --test)
├── src/
│ ├── server.js, app.js, config.js
│ ├── db/ # pool Postgres, migration runner, migrations SQL
│ ├── auth/ # OIDC (Authentik), session, middleware, routes /auth
│ ├── routes/ # routes API /api/*
│ ├── repositories/ # accès SQL par table
│ └── lib/ # helpers purs (validation YouTube/Spotify) — couverts par les tests
└── public/
├── *.html # une page par fonctionnalité
├── css/style.css
└── js/ # fetch wrapper, rendu, logique par page