Next-Step est un projet "Job Board" (plateforme d'offres d'emploi) composé d'un frontend en Next.js et d'un backend en Flask. Il utilise PostgreSQL pour la base de données et MinIO pour le stockage des fichiers (CV, photos, logos, lettres de motivation).
- Frontend : Next.js — routage simple, riche écosystème React.
- Backend : Flask — léger, adapté aux APIs.
- Base de données : PostgreSQL — fiable et supporte UUID.
- Stockage : MinIO — stockage d'objets pour fichiers volumineux, interface graphique.
- Auth & sécurité : JWT + Flask-Bcrypt — auth sans état et mots de passe hachés.
- ORM : Flask-SQLAlchemy — mapping Python ↔ Postgres et création automatique des tables.
Liens officiels pour l'installation de toutes les prérequis du projet :
- Docker : https://docs.docker.com/get-docker/
- Python : https://www.python.org/downloads/
- Node.js : https://nodejs.org/en/download/
- Nextjs : https://nextjs.org/docs/app/getting-started/installation
- Flask : https://flask.palletsprojects.com/en/stable/installation/
- Git : https://git-scm.com/downloads
- IDE (par exemple VSCode) : https://code.visualstudio.com/download
Clonez le dépôt si ce n'est pas déjà fait :
git clone https://github.com/EpitechMscProPromo2028/T-WEB-501-PAR_1
cd T-WEB-501-PAR_1Depuis la racine du projet (là où se trouve docker-compose.yml, donc juste après avoir fait cd T-WEB-501-PAR_1), lancez :
sudo docker compose up -dSi cette commande ne fonctionne pas, la commande doit probablement être :
sudo docker-compose up -d(La commande peut varier selon votre OS — voir la documentation Docker ci-dessus.)
Créez flask/.env contenant :
DATABASE_URL=postgresql+psycopg://user:password@localhost:5432/webapp
MINIO_ENDPOINT=localhost:9000
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin123
MINIO_BUCKET=documents
MINIO_SECURE=False
SECRET_KEY=une_clé_secrète_pour_votre_environnement
Le fichier .env contient les variables lues par l'application. Le fichier flask/config.py utilise python-dotenv et os.environ.get(...) pour peupler Config (ex. Config.SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')). Modifier .env change uniquement la configuration sans toucher au code.
DATABASE_URL: connexion à Postgres.MINIO_*: connexion à MinIO.SECRET_KEY: clé secrète pour JWT (générer une valeur aléatoire et secrète).
Pour générer rapidement une SECRET_KEY :
python3 -c "import os; print(os.urandom(24).hex())"(La commande peut varier selon l'OS — voir la documentation Python.)
Toujours depuis la racine du projet :
python3 -m venv .venv
source .venv/bin/activate
pip install -r flask/requirements.txt
python3 flask/app.pyCes commandes créent/activent un environnement virtuel, installent les dépendances Python et démarrent le serveur Flask (par défaut sur http://localhost:5001).
Dans un autre terminal :
cd next-step
npm install
npm run devLe serveur de développement Next.js écoute par défaut sur http://localhost:3000.
Cette base de données PostgreSQL a été conçue pour fonctionner sans aucune configuration manuelle. Elle se déploie automatiquement via Docker et s'initialise lors du premier lancement de l'application Flask. L'objectif est d'avoir un système de données opérationnel immédiatement, avec toutes les tables et relations créées automatiquement par SQLAlchemy.
L'environnement utilise Docker Compose pour isoler et orchestrer les services de base de données. Cette approche garantit un environnement reproductible, simplifie le déploiement et évite les conflits de versions entre les différents environnements de développement.
- Base :
webapp - Utilisateur :
user/password - Port :
5432(mappé sur l'hôte)
- Ports :
9000(API) /9001(Console Web (admin)) - Accès :
minioadmin/minioadmin123
Les identifiants utilisent des UUID v4 plutôt que des entiers auto-incrémentés. Cette approche garantit des identifiants uniques et imprévisibles, empêchant ainsi de deviner les IDs d'autres enregistrements par simple incrémentation. Les UUID sont également plus scalables car ils peuvent être générés côté application sans conflit, même avec plusieurs bases de données distribuées.
Les mots de passe sont sécurisés avec Flask-Bcrypt qui utilise l'algorithme bcrypt pour le hachage. Les mots de passe ne sont jamais stockés en clair dans la base de données - seuls les hashs sont conservés. Le modèle People propose deux méthodes :
set_password(password): hache et stocke le mot de passecheck_password(password): vérifie si le mot de passe correspond au hash stocké
app.config['SQLALCHEMY_DATABASE_URI'] = Config.SQLALCHEMY_DATABASE_URI
db.init_app(app)
# Import des modèles
from models.people import People
from models.companies import Companies
# ...
# Création automatique des tables
with app.app_context():
db.create_all() # Analyse les modèles et crée le schémaFlask-SQLAlchemy analyse automatiquement :
- Les classes de modèles
- Les relations entre les tables (
ForeignKey,relationship) - Les liens entre les données
- Génère les
CREATE TABLEcorrespondants
| Table | Contenu | Relations |
|---|---|---|
| people | Profils utilisateurs (UUID, nom, email, CV, photo) | → education, experience, skills, candidatures |
| companies | Entreprises (UUID, nom, logo, email) | → ads |
| ads | Offres (UUID, titre, description, salaire) | company_id, category_id |
| category | Catégories d'emploi (UUID, nom) | → ads |
| candidatures | Postulations (person_id, ads_id, date) | people ↔ ads |
| education | Formations (person_id, école, dates) | → people |
| experience | Expériences pro (person_id, entreprise, dates) | → people |
| skills | Compétences (person_id, nom) | → people |
MinIO sert de serveur de stockage de document pour gérer tous les fichiers de l'application. Ce choix permet de séparer les documents/images (images, PDFs) de la base de données relationnelle, offrant une meilleure performance et une scalabilité native avec distribution et réplication.
Les fichiers sont organisés dans un bucket principal documents avec des dossiers spécifiques : CV_fichier pour les CVs, Photo_de_profile pour les photos de profil, Logo_entreprise pour les logos des entreprises, et Lettre_de_motivation pour les lettres de motivation. Cette configuration se met en place automatiquement au démarrage.
Exemple avec la table Skills qui démontre l'utilisation des UUID et des relations :
class Skills(db.Model):
__tablename__ = 'skills'
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
person_id = db.Column(UUID(as_uuid=True), ForeignKey('people.id'), nullable=False)
name = db.Column(db.String(100), nullable=False)
person = db.relationship('People', backref=db.backref('skills'))Flask-SQLAlchemy génère automatiquement les liaisons entre les tables. La relation backref permet d'accéder aux compétences depuis un objet People via person.skills.
- Flask-SQLAlchemy : ORM Python ↔ PostgreSQL
- UUID : Identifiants cryptographiquement sûrs
- Docker Compose : Orchestration multi-conteneurs
- MinIO : Stockage de document
- Flask-Bcrypt : Hachage sécurisé des mots de passe
Dans cette étape, nous avons développé le composant AdsCard.jsx pour afficher les offres d'emploi sous forme de cartes.
- Le titre de l'annonce
- Le type de contrat et la catégorie, affichés dans des bulles colorées
- La description de l'offre, tronquée si elle est trop longue
- Un bouton "Plus d'informations" (actuellement non fonctionnel)
Le composant utilise Tailwind CSS pour le style et FontAwesome pour les icônes.
On a également ajouté des données fictives dans page.js pour simuler quatre offres d'emploi et vérifier le bon fonctionnement des cartes.
Dans cette étape, nous avons rendu le bouton "Plus d'informations" fonctionnel dans le composant AdsCard.jsx. En utilisant l'état React (useState), le bouton permet désormais de basculer entre l'affichage condensé et l'affichage complet des détails de l'offre sans recharger la page.
- Un clic sur le bouton "Plus d'informations" révèle les détails supplémentaires (entreprise, localisation, salaire, durée, diplôme requis) avec une animation fluide.
- La description complète est affichée uniquement en mode étendu, et la description tronquée est cachée.
- Le bouton change de texte et d'icône (flèche vers le haut/bas) selon l'état d'expansion.
Cette API REST sert de backend pour Next-Step, offrant toutes les fonctionnalités nécessaires pour gérer les utilisateurs, entreprises, offres d'emploi et candidatures. L'architecture modulaire basée sur des blueprints Flask garantit une séparation claire des responsabilités et facilite la maintenance du code.
L'API utilise une architecture Blueprint qui organise les routes par domaine fonctionnel. Chaque blueprint gère un aspect spécifique de l'application :
from routes import (
ads_bp, candidatures_bp, category_bp, companies_bp,
education_bp, experience_bp, file_bp, people_bp, search_bp, skills_bp
)
app.register_blueprint(file_bp) # Gestion des fichiers
app.register_blueprint(search_bp) # Recherche avancéeCette approche permet de grouper logiquement les endpoints et d'appliquer des middlewares spécifiques par domaine.
GET /ads/<uuid>: Récupérer une offre spécifiquePOST /ads: Créer une nouvelle offre d'emploiPUT /ads/<uuid>: Modifier une offre existanteDELETE /ads/<uuid>: Supprimer une offre
GET /search?sector_activity=IT&post_title=Developer&salary_min=50000&salary_max=80000Le moteur de recherche utilise SQLAlchemy Query Builder avec jointures pour filtrer les offres selon multiple critères : secteur, titre, diplôme requis, et fourchette salariale.
GET /file/profile_photo/<uuid>: Accès sécurisé aux photos de profilGET /file/cv/<uuid>: Téléchargement des CVsGET /file/company_logo/<uuid>: Logos des entreprises
Cross-Origin Resource Sharing activé pour permettre les requêtes depuis le frontend Next.js hébergé sur un port différent :
from flask_cors import CORS
CORS(app) # Autorise toutes les origines en développementLes données sont validées côté client (frontend) avec des champs required, puis traitées par des fonctions utilitaires comme return_ad() qui standardise le format de réponse JSON :
def return_ad(ad):
return {
"id": str(ad.id),
"title": ad.title,
"description": ad.description,
"salary": ad.salary,
"category_id": str(ad.category_id)
}Système unifié de gestion d'exceptions avec rollback automatique des transactions :
try:
db.session.add(user)
db.session.commit()
return jsonify({"message": "Success"}), 201
except Exception as e:
db.session.rollback()
return jsonify({"error": str(e)}), 500MinIO S3 intégré via un service dédié pour la gestion centralisée des fichiers :
def upload_file(file_data, folder, entity_id):
client = Minio(Config.MINIO_ENDPOINT, ...)
file_name_store = f"{folder}/{entity_id}{file_data.filename}"
client.put_object(Config.MINIO_BUCKET, file_name_store, file_data)Les fichiers sont organisés par type (CV_fichier/, Photo_de_profile/, Logo_entreprise/) avec des URLs pré-signées pour l'accès sécurisé.
Variables d'environnement centralisées via python-dotenv :
class Config:
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
SECRET_KEY = os.environ.get('SECRET_KEY')
MINIO_ENDPOINT = os.environ.get('MINIO_ENDPOINT')Cette approche sépare la configuration du code et facilite le déploiement multi-environnements.
GET /search?post_title=Developer&salary_min=50000Cet endpoint retourne toutes les offres contenant "Developer" dans le titre avec un salaire minimum de 50 000€, démontrant la simplicité d'utilisation de l'API.
- Flask-SQLAlchemy : ORM avec support UUID natif
- Flask-CORS : Communication cross-origin
- MinIO Python SDK : Stockage S3-compatible
- python-dotenv : Configuration d'environnement
Le système d'authentification utilise le blueprint auth_bp avec JSON Web Tokens pour sécuriser l'API sans état de session côté serveur.
L'authentification par token remplace les sessions traditionnelles par des jetons cryptographiques. Après connexion, le serveur génère un token JWT contenant les informations utilisateur. Cette approche sans état améliore la scalabilité, la sécurité cryptographique et les performances en évitant les requêtes base de données répétées.
Le système utilise une clé secrète définie dans les variables d'environnement :
# .env
SECRET_KEY=your-super-secret-key-here
# config.py
SECRET_KEY = os.environ.get('SECRET_KEY')Cette clé signe et valide tous les tokens JWT générés par l'application.
POST /auth/register
{
"email": "user@example.com",
"password": "password123",
"first_name": "John",
"last_name": "Doe"
}Supporte l'upload simultané de fichiers (CV, photo de profil) et génère automatiquement un token JWT.
POST /auth/login
{
"email": "user@example.com",
"password": "password123"
}Flask-Bcrypt hache les mots de passe avec l'algorithme bcrypt - aucun mot de passe en clair n'est stocké :
def set_password(self, password):
self.password_hash = bcrypt.generate_password_hash(password).decode('utf-8')Les tokens JWT incluent l'ID utilisateur et sont signés avec la clé secrète pour validation côté client.
- Flask-JWT : Authentification sans état
- Flask-Bcrypt : Hachage sécurisé
- MinIO Integration : Upload de fichiers