transfer from private server/git server
This commit is contained in:
197
web/app.py
197
web/app.py
@@ -1,152 +1,87 @@
|
||||
import os
|
||||
import uuid
|
||||
import json
|
||||
# J'ai ajouté 'request' aux imports
|
||||
from flask import Flask, redirect, url_for, jsonify, session, render_template, request
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
"""
|
||||
app.py — Point d'entrée de l'application GestHub.
|
||||
|
||||
Ce fichier ne fait qu'assembler l'application (factory pattern) : création
|
||||
de l'app Flask, enregistrement des blueprints (couche routes/), du client
|
||||
OAuth Keycloak, et des gestionnaires d'erreurs globaux. Aucune logique
|
||||
métier ni aucun accès base de données n'a lieu ici (cf. architecture
|
||||
5 couches — Bloc 1, C3).
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from authlib.integrations.flask_client import OAuth
|
||||
from flask import Flask, jsonify, render_template
|
||||
|
||||
app = Flask(__name__)
|
||||
ANNOUNCE_FILE = os.path.join(os.path.dirname(__file__), "annonces.json")
|
||||
# Ta config DB actuelle
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://flaskuser:flaskpass@mariadb/flaskdb'
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
app.secret_key = os.environ.get("SECRET_KEY", "dev-key")
|
||||
from config import Config
|
||||
from db import init_db
|
||||
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
# --- MODELE DE DONNEES POUR LES BLOCS ---
|
||||
class Block(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
block_type = db.Column(db.String(50)) # ex: 'iframe', 'buttons', 'html'
|
||||
column_name = db.Column(db.String(20)) # ex: 'left', 'center', 'right'
|
||||
position = db.Column(db.Integer) # pour l'ordre (0, 1, 2...)
|
||||
data = db.Column(db.Text) # Contenu JSON (url, titre, etc.)
|
||||
def create_app(config_class=Config):
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(config_class)
|
||||
app.secret_key = config_class.SECRET_KEY
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"type": self.block_type,
|
||||
"column": self.column_name,
|
||||
"position": self.position,
|
||||
"data": json.loads(self.data) if self.data else {}
|
||||
}
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# Création des tables si elles n'existent pas
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
# --- Base de données (couche infrastructure) ---
|
||||
db = init_db(config_class)
|
||||
app.config["DB"] = db
|
||||
|
||||
# ... (Ici, garde ta configuration OAUTH et tes routes Login/Logout/Auth inchangées) ...
|
||||
# ... (Garde aussi tes fonctions load_announces, save_announces etc.) ...
|
||||
# --- OAuth / OIDC Keycloak ---
|
||||
oauth = OAuth(app)
|
||||
|
||||
oauth = OAuth(app)
|
||||
keycloak = oauth.register(
|
||||
name='keycloak',
|
||||
client_id='flask-app',
|
||||
client_secret='T5G5jzCBiphnBNh9uuj0f6YNc9HrP8r4',
|
||||
server_metadata_url='https://keycloak.ninolbt.com/realms/gesthub/.well-known/openid-configuration',
|
||||
client_kwargs={'scope': 'openid profile email'}
|
||||
)
|
||||
# --- Blueprints (couche routes/) ---
|
||||
from routes.auth import init_auth_routes
|
||||
from routes.dashboard import bp as dashboard_bp
|
||||
from routes.annonces import bp as annonces_bp
|
||||
from routes.fichiers import bp as fichiers_bp
|
||||
from routes.evenements import bp as evenements_bp
|
||||
from routes.rgpd import bp as rgpd_bp
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
user = session.get('user')
|
||||
if user:
|
||||
return render_template('view/index.html', user=user)
|
||||
return redirect(url_for('login'))
|
||||
app.register_blueprint(init_auth_routes(oauth))
|
||||
app.register_blueprint(dashboard_bp)
|
||||
app.register_blueprint(annonces_bp)
|
||||
app.register_blueprint(fichiers_bp)
|
||||
app.register_blueprint(evenements_bp)
|
||||
app.register_blueprint(rgpd_bp)
|
||||
|
||||
@app.route('/login')
|
||||
def login():
|
||||
nonce = uuid.uuid4().hex
|
||||
session['nonce'] = nonce
|
||||
redirect_uri = url_for('auth', _external=True, _scheme='https')
|
||||
return keycloak.authorize_redirect(redirect_uri, nonce=nonce)
|
||||
register_error_handlers(app)
|
||||
return app
|
||||
|
||||
@app.route('/auth')
|
||||
def auth():
|
||||
token = keycloak.authorize_access_token()
|
||||
nonce = session.pop('nonce', None)
|
||||
userinfo = keycloak.parse_id_token(token, nonce=nonce)
|
||||
session['user'] = userinfo
|
||||
session["id_token"] = token.get("id_token")
|
||||
return redirect('/')
|
||||
|
||||
@app.route("/logout")
|
||||
def logout():
|
||||
id_token = session.get("id_token")
|
||||
session.clear()
|
||||
return redirect(
|
||||
f"https://keycloak.ninolbt.com/realms/gesthub/protocol/openid-connect/logout"
|
||||
f"?post_logout_redirect_uri=https://dashboard.ninolbt.com"
|
||||
f"&id_token_hint={id_token}"
|
||||
)
|
||||
def register_error_handlers(app):
|
||||
"""Gestionnaires d'erreurs globaux (Bloc 1 - C5) : réponses uniformisées,
|
||||
aucune fuite d'information système sensible (stack trace, chemin serveur...).
|
||||
"""
|
||||
|
||||
# --- API LAYOUT (Gestion des Blocs) ---
|
||||
@app.errorhandler(403)
|
||||
def forbidden(_error):
|
||||
if _wants_json():
|
||||
return jsonify({"error": "forbidden"}), 403
|
||||
return render_template("errors/403.html"), 403
|
||||
|
||||
@app.route('/api/layout', methods=['GET'])
|
||||
def get_layout():
|
||||
# Récupère tous les blocs triés par position
|
||||
blocks = Block.query.order_by(Block.position).all()
|
||||
return jsonify([b.to_dict() for b in blocks])
|
||||
@app.errorhandler(404)
|
||||
def not_found(_error):
|
||||
if _wants_json():
|
||||
return jsonify({"error": "not_found"}), 404
|
||||
return render_template("errors/404.html"), 404
|
||||
|
||||
@app.route('/api/layout/save', methods=['POST'])
|
||||
def save_layout():
|
||||
# Sauvegarde l'ordre et la colonne après un drag & drop
|
||||
user = session.get("user")
|
||||
if not user or "/admin" not in user.get("groups", []):
|
||||
return jsonify({"error": "Unauthorized"}), 403
|
||||
@app.errorhandler(500)
|
||||
def server_error(error):
|
||||
app.logger.exception("Erreur interne non gérée: %s", error)
|
||||
if _wants_json():
|
||||
return jsonify({"error": "internal_error"}), 500
|
||||
return render_template("errors/500.html"), 500
|
||||
|
||||
layout_data = request.json # Liste de {id, column, position}
|
||||
|
||||
for item in layout_data:
|
||||
block = Block.query.get(item['id'])
|
||||
if block:
|
||||
block.column_name = item['column']
|
||||
block.position = item['position']
|
||||
|
||||
db.session.commit()
|
||||
return jsonify({"status": "saved"})
|
||||
|
||||
@app.route('/api/block/add', methods=['POST'])
|
||||
def add_block():
|
||||
user = session.get("user")
|
||||
if not user or "/admin" not in user.get("groups", []):
|
||||
return jsonify({"error": "Unauthorized"}), 403
|
||||
def _wants_json():
|
||||
from flask import request
|
||||
|
||||
data = request.json
|
||||
new_block = Block(
|
||||
block_type=data.get('type'),
|
||||
column_name=data.get('column', 'center'),
|
||||
position=99, # Ajoute à la fin par défaut
|
||||
data=json.dumps(data.get('data', {}))
|
||||
)
|
||||
db.session.add(new_block)
|
||||
db.session.commit()
|
||||
return jsonify(new_block.to_dict())
|
||||
return request.path.startswith("/api/")
|
||||
|
||||
@app.route('/api/block/<int:block_id>', methods=['DELETE'])
|
||||
def delete_block(block_id):
|
||||
user = session.get("user")
|
||||
if not user or "/admin" not in user.get("groups", []):
|
||||
return jsonify({"error": "Unauthorized"}), 403
|
||||
|
||||
block = Block.query.get(block_id)
|
||||
if block:
|
||||
db.session.delete(block)
|
||||
db.session.commit()
|
||||
return jsonify({"status": "deleted"})
|
||||
return jsonify({"error": "not found"}), 404
|
||||
app = create_app()
|
||||
|
||||
# --- API ANNONCES (Tes routes existantes) ---
|
||||
# ... (Colle ici tes routes /api/annonces existantes, elles sont très bien) ...
|
||||
# ... (N'oublie pas la route /api/is_admin) ...
|
||||
|
||||
@app.route("/api/is_admin")
|
||||
def is_admin():
|
||||
user = session.get("user")
|
||||
# Sécurité : si pas de user, renvoie false
|
||||
if not user: return jsonify({"admin": False})
|
||||
return jsonify({"admin": "/admin" in user.get("groups", [])})
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', debug=True)
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=5000, debug=Config.DEBUG)
|
||||
|
||||
Reference in New Issue
Block a user