alpinux.site.2026/dynamic/app.py
Cédrix 27847dfad3 feat: dynamic.alpinux.org — quiz interactifs Flask + AlpID OIDC
App Flask complète pour https://dynamic.alpinux.org :
- 10 quiz Linux, 5 niveaux (Découverte → Expert), 50+ questions
- Public : Découverte, Débutant, Intermédiaire (6 quiz)
- Membres AlpID : Avancé, Expert (4 quiz — Git, Admin, Sécurité, Bash)
- Navigation question par question avec avance automatique après choix
- Score calculé côté serveur, enregistré en SQLite si connecté
- Page profil : meilleurs scores par quiz + historique des tentatives

Authentification :
- OIDC via authlib + AlpID (Keycloak), SSO partagé avec Gitea/Nextcloud
- Décorateur @login_required, redirection post-login sur l'URL d'origine
- /auth/login, /auth/callback, /auth/logout

Structure :
- dynamic/app.py, db.py, quiz.py, auth_utils.py
- dynamic/routes/ (public.py, auth.py, protected.py)
- dynamic/templates/ (base, index, quiz/*, profil/)
- dynamic/static/ (style.css thème Alpinux, quiz.js vanilla)
- dynamic/data/quizzes.json (source de vérité des questions)
- dynamic/.env.example

Infrastructure :
- scripts/dynamic.alpinux.org.vhost.conf (Apache reverse proxy)
- scripts/dynamic.alpinux.org.service (systemd Gunicorn)
- docs/technique/deploiement-dynamic.md (procédure complète)
- mkdocs.yml : page de déploiement ajoutée à la nav Technique
- .gitignore : exclut venv/ et .env

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 12:09:37 +02:00

46 lines
1.1 KiB
Python

import os
from flask import Flask
from authlib.integrations.flask_client import OAuth
from db import close_db, init_db
from routes.public import public_bp
from routes.auth import auth_bp
from routes.protected import protected_bp
def create_app():
app = Flask(__name__)
app.secret_key = os.environ['SECRET_KEY']
# OIDC AlpID
oauth = OAuth(app)
oauth.register(
name='alpid',
server_metadata_url=os.environ['ALPID_DISCOVERY_URL'],
client_id=os.environ['ALPID_CLIENT_ID'],
client_secret=os.environ['ALPID_CLIENT_SECRET'],
client_kwargs={'scope': 'openid profile email'},
)
app.extensions['oauth'] = oauth
# Filtre Jinja2 utilitaire
app.jinja_env.filters['enumerate'] = enumerate
# Blueprints
app.register_blueprint(public_bp)
app.register_blueprint(auth_bp, url_prefix='/auth')
app.register_blueprint(protected_bp)
# Base de données
app.teardown_appcontext(close_db)
with app.app_context():
init_db()
return app
app = create_app()
if __name__ == '__main__':
app.run(debug=os.environ.get('FLASK_DEBUG', '0') == '1')