Files
Dragonbank/DragonBank/backend/app.py
T

448 lines
17 KiB
Python
Raw Normal View History

2026-03-18 16:20:09 +01:00
"""
DragonBank - Backend API
========================
2026-03-27 17:52:41 +01:00
Point d'entree de l'API REST DragonBank.
Ce fichier ne contient que les routes Flask.
Toute la logique est deleguee aux classes metier (Services) :
config.py — Constantes et parametres
database.py — Connexion et serialisation
auth.py — JWT et protection des routes
validators.py — Validation des donnees entrantes
services/
auth_service.py - Inscription et connexion
utilisateur_service.py - Profil utilisateur
compte_service.py - Gestion des comptes
beneficiaire_service.py- Gestion des beneficiaires
transaction_service.py - Virements et historique
stats_service.py - Statistiques tableau de bord
models/
simulateur.py — Logique calcul interets composes
Version : 3.0 (Refactoring Service Layer)
2026-03-18 16:20:09 +01:00
"""
2026-03-27 17:52:41 +01:00
import logging
from datetime import datetime, timezone
2026-03-18 16:20:09 +01:00
from flask import Flask, request, jsonify
from flask_cors import CORS
2026-03-27 17:52:41 +01:00
from config import CLE_SECRETE
from database import creer_connexion
from auth import token_requis
from validators import valider_parametres_simulateur
from services.auth_service import AuthService
from services.utilisateur_service import UtilisateurService
from services.compte_service import CompteService
from services.beneficiaire_service import BeneficiaireService
from services.transaction_service import TransactionService
from services.stats_service import StatsService
from models.simulateur import Simulateur
# ============================================================
# INITIALISATION
# ============================================================
2026-03-18 16:20:09 +01:00
app = Flask(__name__)
CORS(app)
2026-03-27 17:52:41 +01:00
app.config['SECRET_KEY'] = CLE_SECRETE
2026-03-18 16:20:09 +01:00
2026-03-27 17:52:41 +01:00
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(name)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
journaliseur = logging.getLogger('dragonbank')
2026-03-18 16:20:09 +01:00
2026-03-27 17:52:41 +01:00
# Instances des services (sans etat, reutilisables)
auth_service = AuthService()
utilisateur_service = UtilisateurService()
compte_service = CompteService()
beneficiaire_service = BeneficiaireService()
transaction_service = TransactionService()
stats_service = StatsService()
simulateur = Simulateur()
2026-03-18 16:20:09 +01:00
2026-03-27 17:52:41 +01:00
# ============================================================
# SANTE
# ============================================================
2026-03-18 16:20:09 +01:00
@app.route('/api/health', methods=['GET'])
2026-03-27 17:52:41 +01:00
def verification_sante():
"""Healthcheck pour Docker et load balancers."""
2026-03-18 16:20:09 +01:00
try:
2026-03-27 17:52:41 +01:00
conn = creer_connexion()
conn.cursor().execute('SELECT 1')
2026-03-18 16:20:09 +01:00
conn.close()
return jsonify({
2026-03-27 17:52:41 +01:00
'status': 'healthy',
'service': 'DragonBank Backend API',
'version': '3.0',
'database': 'connected',
2026-03-18 16:20:09 +01:00
'timestamp': datetime.now(timezone.utc).isoformat()
}), 200
except Exception as e:
2026-03-27 17:52:41 +01:00
journaliseur.error("Health check echoue : %s", str(e))
return jsonify({'status': 'unhealthy', 'error': str(e)}), 500
2026-03-18 16:20:09 +01:00
2026-03-27 17:52:41 +01:00
# ============================================================
# AUTHENTIFICATION
# ============================================================
2026-03-18 16:20:09 +01:00
@app.route('/api/auth/register', methods=['POST'])
2026-03-27 17:52:41 +01:00
def inscription():
"""Cree un nouveau compte utilisateur avec un compte courant de bienvenue."""
donnees = request.get_json(silent=True)
2026-03-18 16:20:09 +01:00
try:
2026-03-27 17:52:41 +01:00
resultat = auth_service.inscrire(donnees)
return jsonify(resultat), 201
except ValueError as e:
return jsonify({'error': str(e)}), 400
2026-03-18 16:20:09 +01:00
except Exception as e:
2026-03-27 17:52:41 +01:00
journaliseur.error("Erreur inscription : %s", str(e))
return jsonify({'error': "Erreur interne lors de l'inscription"}), 500
2026-03-18 16:20:09 +01:00
@app.route('/api/auth/login', methods=['POST'])
2026-03-27 17:52:41 +01:00
def connexion_utilisateur():
"""Authentifie un utilisateur et retourne un token JWT."""
donnees = request.get_json(silent=True)
2026-03-18 16:20:09 +01:00
try:
2026-03-27 17:52:41 +01:00
resultat = auth_service.connecter(donnees)
return jsonify(resultat), 200
except ValueError as e:
return jsonify({'error': str(e)}), 400
except PermissionError as e:
return jsonify({'error': str(e)}), 403
2026-03-18 16:20:09 +01:00
except Exception as e:
2026-03-27 17:52:41 +01:00
journaliseur.error("Erreur connexion : %s", str(e))
return jsonify({'error': 'Erreur interne lors de la connexion'}), 500
2026-03-18 16:20:09 +01:00
2026-03-27 17:52:41 +01:00
# ============================================================
# PROFIL UTILISATEUR
# ============================================================
2026-03-18 16:20:09 +01:00
@app.route('/api/user/profile', methods=['GET'])
2026-03-27 17:52:41 +01:00
@token_requis
def obtenir_profil(id_utilisateur_courant):
"""Retourne le profil de l'utilisateur connecte."""
2026-03-18 16:20:09 +01:00
try:
2026-03-27 17:52:41 +01:00
utilisateur = utilisateur_service.obtenir_profil(id_utilisateur_courant)
return jsonify({'user': utilisateur}), 200
except ValueError as e:
return jsonify({'error': str(e)}), 404
except Exception as e:
return jsonify({'error': 'Erreur interne lors de la recuperation du profil'}), 500
2026-03-18 16:20:09 +01:00
2026-03-27 17:52:41 +01:00
@app.route('/api/user/profile', methods=['PUT'])
@token_requis
def modifier_profil(id_utilisateur_courant):
"""Met a jour les champs non sensibles du profil."""
donnees = request.get_json(silent=True)
try:
mis_a_jour = utilisateur_service.modifier_profil(id_utilisateur_courant, donnees)
return jsonify({'message': 'Profil mis a jour', 'user': mis_a_jour}), 200
except ValueError as e:
return jsonify({'error': str(e)}), 400
except Exception as e:
return jsonify({'error': 'Erreur lors de la mise a jour'}), 500
@app.route('/api/user/security/password', methods=['PUT'])
@token_requis
def modifier_mot_de_passe(id_utilisateur_courant):
"""Met a jour le mot de passe utilisateur (necessite le mot de passe actuel)."""
donnees = request.get_json(silent=True)
try:
utilisateur_service.changer_mot_de_passe(id_utilisateur_courant, donnees)
return jsonify({'message': 'Mot de passe mis a jour avec succes'}), 200
except ValueError as e:
return jsonify({'error': str(e)}), 400
except PermissionError as e:
return jsonify({'error': str(e)}), 403
except Exception as e:
journaliseur.error("Erreur modification mot de passe : %s", str(e))
return jsonify({'error': 'Erreur interne'}), 500
@app.route('/api/user/security/email', methods=['PUT'])
@token_requis
def modifier_email(id_utilisateur_courant):
"""Met a jour l'adresse email (necessite le mot de passe actuel)."""
donnees = request.get_json(silent=True)
try:
utilisateur_service.changer_email(id_utilisateur_courant, donnees)
return jsonify({'message': 'Email mis a jour avec succes. Veuillez vous reconnecter avec votre nouvel email.'}), 200
except ValueError as e:
return jsonify({'error': str(e)}), 400
except PermissionError as e:
return jsonify({'error': str(e)}), 403
except Exception as e:
journaliseur.error("Erreur modification email : %s", str(e))
return jsonify({'error': 'Erreur interne'}), 500
# ============================================================
# COMPTES BANCAIRES
# ============================================================
2026-03-18 16:20:09 +01:00
@app.route('/api/accounts', methods=['GET'])
2026-03-27 17:52:41 +01:00
@token_requis
def obtenir_comptes(id_utilisateur_courant):
"""Retourne la liste des comptes actifs."""
2026-03-18 16:20:09 +01:00
try:
2026-03-27 17:52:41 +01:00
comptes = compte_service.obtenir_comptes(id_utilisateur_courant)
return jsonify({'accounts': comptes}), 200
except Exception:
return jsonify({'error': 'Erreur lors de la recuperation des comptes'}), 500
2026-03-18 16:20:09 +01:00
@app.route('/api/accounts', methods=['POST'])
2026-03-27 17:52:41 +01:00
@token_requis
def ouvrir_compte(id_utilisateur_courant):
"""Ouvre un nouveau compte bancaire."""
donnees = request.get_json(silent=True)
2026-03-18 16:20:09 +01:00
try:
2026-03-27 17:52:41 +01:00
compte = compte_service.ouvrir_compte(id_utilisateur_courant, donnees)
return jsonify({'message': 'Compte ouvert avec succes', 'account': compte}), 201
except ValueError as e:
return jsonify({'error': str(e)}), 400 if 'invalide' in str(e) else 409
2026-03-18 16:20:09 +01:00
except Exception as e:
2026-03-27 17:52:41 +01:00
journaliseur.error("Erreur ouverture compte : %s", str(e))
return jsonify({'error': "Erreur lors de l'ouverture du compte"}), 500
2026-03-18 16:20:09 +01:00
2026-03-27 17:52:41 +01:00
@app.route('/api/accounts/<id_compte>', methods=['GET'])
@token_requis
def obtenir_detail_compte(id_utilisateur_courant, id_compte):
"""Retourne les details d'un compte specifique."""
2026-03-18 16:20:09 +01:00
try:
2026-03-27 17:52:41 +01:00
compte = compte_service.obtenir_detail_compte(id_utilisateur_courant, id_compte)
return jsonify({'account': compte}), 200
except ValueError as e:
return jsonify({'error': str(e)}), 404
except Exception:
return jsonify({'error': 'Erreur serveur'}), 500
2026-03-18 16:20:09 +01:00
2026-03-27 17:52:41 +01:00
@app.route('/api/accounts/<id_compte>/history', methods=['GET'])
@token_requis
def historique_solde(id_utilisateur_courant, id_compte):
"""Retourne l'evolution du solde pour le graphique."""
try:
historique = compte_service.historique_solde(id_utilisateur_courant, id_compte)
return jsonify(historique), 200
except ValueError as e:
return jsonify({'error': str(e)}), 404
except Exception:
return jsonify({'error': 'Erreur serveur'}), 500
# ============================================================
# BENEFICIAIRES
# ============================================================
2026-03-18 16:20:09 +01:00
@app.route('/api/beneficiaries', methods=['GET'])
2026-03-27 17:52:41 +01:00
@token_requis
def obtenir_beneficiaires(id_utilisateur_courant):
"""Retourne la liste des beneficiaires."""
2026-03-18 16:20:09 +01:00
try:
2026-03-27 17:52:41 +01:00
beneficiaires = beneficiaire_service.obtenir_beneficiaires(id_utilisateur_courant)
return jsonify({'beneficiaries': beneficiaires}), 200
except Exception:
return jsonify({'error': 'Erreur serveur'}), 500
2026-03-18 16:20:09 +01:00
@app.route('/api/beneficiaries', methods=['POST'])
2026-03-27 17:52:41 +01:00
@token_requis
def ajouter_beneficiaire(id_utilisateur_courant):
"""Ajoute un nouveau beneficiaire."""
donnees = request.get_json(silent=True)
2026-03-18 16:20:09 +01:00
try:
2026-03-27 17:52:41 +01:00
beneficiaire = beneficiaire_service.ajouter_beneficiaire(id_utilisateur_courant, donnees)
return jsonify({'message': 'Beneficiaire ajoute', 'beneficiary': beneficiaire}), 201
except ValueError as e:
return jsonify({'error': str(e)}), 400 if 'vous-meme' in str(e) else 409
2026-03-18 16:20:09 +01:00
except Exception as e:
2026-03-27 17:52:41 +01:00
return jsonify({'error': "Erreur lors de l'ajout"}), 500
2026-03-18 16:20:09 +01:00
2026-03-27 17:52:41 +01:00
@app.route('/api/beneficiaries/<id_beneficiaire>', methods=['DELETE'])
@token_requis
def supprimer_beneficiaire(id_utilisateur_courant, id_beneficiaire):
"""Supprime un beneficiaire."""
2026-03-18 16:20:09 +01:00
try:
2026-03-27 17:52:41 +01:00
beneficiaire_service.supprimer_beneficiaire(id_utilisateur_courant, id_beneficiaire)
return jsonify({'message': 'Beneficiaire supprime'}), 200
except ValueError as e:
return jsonify({'error': str(e)}), 404
2026-03-18 16:20:09 +01:00
except Exception as e:
2026-03-27 17:52:41 +01:00
return jsonify({'error': 'Erreur lors de la suppression'}), 500
2026-03-18 16:20:09 +01:00
2026-03-27 17:52:41 +01:00
# ============================================================
# VIREMENTS
# ============================================================
2026-03-18 16:20:09 +01:00
@app.route('/api/transfers/internal', methods=['POST'])
2026-03-27 17:52:41 +01:00
@token_requis
def virement_interne(id_utilisateur_courant):
"""Virement entre les propres comptes de l'utilisateur."""
donnees = request.get_json(silent=True)
2026-03-18 16:20:09 +01:00
try:
2026-03-27 17:52:41 +01:00
transaction = transaction_service.virement_interne(id_utilisateur_courant, donnees)
return jsonify({'message': 'Virement effectue', 'transaction': transaction}), 200
except ValueError as e:
return jsonify({'error': str(e)}), 400 if 'introuvable' not in str(e) else 404
2026-03-18 16:20:09 +01:00
except Exception as e:
2026-03-27 17:52:41 +01:00
journaliseur.error("Erreur virement interne : %s", str(e))
return jsonify({'error': 'Erreur lors du virement'}), 500
2026-03-18 16:20:09 +01:00
@app.route('/api/transfers/person', methods=['POST'])
2026-03-27 17:52:41 +01:00
@token_requis
def virement_beneficiaire(id_utilisateur_courant):
"""Virement vers un beneficiaire enregistre."""
donnees = request.get_json(silent=True)
2026-03-18 16:20:09 +01:00
try:
2026-03-27 17:52:41 +01:00
transaction = transaction_service.virement_beneficiaire(id_utilisateur_courant, donnees)
return jsonify({'message': 'Virement effectue', 'transaction': transaction}), 200
except ValueError as e:
return jsonify({'error': str(e)}), 400 if 'introuvable' not in str(e) else 404
2026-03-18 16:20:09 +01:00
except Exception as e:
2026-03-27 17:52:41 +01:00
return jsonify({'error': 'Erreur lors du virement'}), 500
2026-03-18 16:20:09 +01:00
@app.route('/api/transfers/external', methods=['POST'])
2026-03-27 17:52:41 +01:00
@token_requis
def virement_externe(id_utilisateur_courant):
"""Virement simule depuis/vers une banque externe."""
donnees = request.get_json(silent=True)
2026-03-18 16:20:09 +01:00
try:
2026-03-27 17:52:41 +01:00
transaction, direction = transaction_service.virement_externe(id_utilisateur_courant, donnees)
sens = 'sortant' if direction == 'outgoing' else 'entrant'
2026-03-18 16:20:09 +01:00
return jsonify({
2026-03-27 17:52:41 +01:00
'message': 'Virement externe ' + sens + ' effectue',
'transaction': transaction
2026-03-18 16:20:09 +01:00
}), 200
2026-03-27 17:52:41 +01:00
except ValueError as e:
return jsonify({'error': str(e)}), 400 if 'introuvable' not in str(e) else 404
2026-03-18 16:20:09 +01:00
except Exception as e:
2026-03-27 17:52:41 +01:00
return jsonify({'error': 'Erreur lors du virement externe'}), 500
2026-03-18 16:20:09 +01:00
2026-03-27 17:52:41 +01:00
# ============================================================
# TRANSACTIONS
# ============================================================
2026-03-18 16:20:09 +01:00
@app.route('/api/transactions', methods=['GET'])
2026-03-27 17:52:41 +01:00
@token_requis
def obtenir_transactions(id_utilisateur_courant):
"""Historique des transactions avec filtres optionnels."""
id_compte = request.args.get('account_id')
type_transaction = request.args.get('type')
limite = request.args.get('limit', 50, type=int)
2026-03-18 16:20:09 +01:00
try:
2026-03-27 17:52:41 +01:00
transactions = transaction_service.obtenir_transactions(
id_utilisateur_courant, id_compte, type_transaction, limite
)
return jsonify({'transactions': transactions, 'count': len(transactions)}), 200
except ValueError as e:
return jsonify({'error': str(e)}), 404
except Exception:
return jsonify({'error': 'Erreur serveur'}), 500
2026-03-18 16:20:09 +01:00
2026-03-27 17:52:41 +01:00
@app.route('/api/transactions/export', methods=['GET'])
@token_requis
def exporter_csv(id_utilisateur_courant):
"""Export CSV de l'historique des transactions."""
id_compte = request.args.get('account_id')
try:
csv_response = transaction_service.exporter_csv(id_utilisateur_courant, id_compte)
return csv_response
except ValueError as e:
return jsonify({'error': str(e)}), 404
except Exception:
return jsonify({'error': 'Erreur serveur'}), 500
# ============================================================
# STATISTIQUES
# ============================================================
2026-03-18 16:20:09 +01:00
@app.route('/api/stats', methods=['GET'])
2026-03-27 17:52:41 +01:00
@token_requis
def obtenir_statistiques(id_utilisateur_courant):
"""Tableau de bord statistique de l'utilisateur."""
2026-03-18 16:20:09 +01:00
try:
2026-03-27 17:52:41 +01:00
stats = stats_service.obtenir_statistiques(id_utilisateur_courant)
return jsonify({'stats': stats}), 200
except Exception:
return jsonify({'error': 'Erreur serveur'}), 500
2026-03-18 16:20:09 +01:00
2026-03-27 17:52:41 +01:00
# ============================================================
# SIMULATEUR D'EPARGNE
# ============================================================
@app.route('/api/simulator', methods=['POST'])
@token_requis
def simuler_epargne(id_utilisateur_courant):
"""Simulation de croissance d'epargne par interets composes."""
donnees = request.get_json(silent=True)
try:
if not donnees:
raise ValueError("Donnees JSON manquantes")
capital = float(donnees.get('capital_initial', 0))
taux = float(donnees.get('taux_annuel', 0))
duree = int(donnees.get('duree_annees', 1))
versement = float(donnees.get('versement_mensuel', 0))
valider_parametres_simulateur(capital, taux, duree, versement)
resultat = simulateur.simuler(capital, taux, duree, versement)
return jsonify(resultat), 200
except (TypeError, ValueError) as e:
return jsonify({'error': str(e)}), 400
# ============================================================
# GESTION DES ERREURS HTTP
# ============================================================
@app.errorhandler(404)
def erreur_404(e):
return jsonify({'error': 'Route introuvable'}), 404
@app.errorhandler(405)
def erreur_405(e):
return jsonify({'error': 'Methode HTTP non autorisee'}), 405
@app.errorhandler(500)
def erreur_500(e):
journaliseur.error("Erreur interne : %s", str(e))
return jsonify({'error': 'Erreur interne du serveur'}), 500
# ============================================================
# POINT D'ENTREE
# ============================================================
2026-03-18 16:20:09 +01:00
if __name__ == '__main__':
2026-03-27 17:52:41 +01:00
from config import URL_BASE_DE_DONNEES
print("DragonBank Backend API v3.0 - demarrage...")
print("Base : " + (URL_BASE_DE_DONNEES.split('@')[1] if '@' in URL_BASE_DE_DONNEES else 'configuree'))
app.run(host='0.0.0.0', port=5000, debug=False)