Akazi (mot Kirundi signifiant "travail") est un outil full-stack de suivi de candidatures, augmenté d'un agent IA autonome qui recherche des offres d'emploi, les fait correspondre au profil de chaque utilisateur et remplit son dashboard automatiquement.
Ce projet sert aussi de démonstrateur technique : architecture cloud, CI/CD, Infrastructure as Code, systèmes agentiques (LangGraph) et bonnes pratiques DevOps.
Le dashboard : les candidatures badgées « Assistant » ont été trouvées et créées par l'agent autonome.
L'assistant conversationnel : recherche d'offres en langage naturel, avec scores de matching sémantique.
Les paramètres : activation de l'agent, recherches sauvegardées, fréquence d'exécution, thème.
Stack technique :
| Couche | Technologies |
|---|---|
| Backend | Java 21, Spring Boot 4, Spring Security, JWT, JPA/Hibernate |
| Frontend | Next.js 15, TypeScript, React 18, Tailwind CSS v4 |
| Agent IA | Python 3.12, FastAPI, LangGraph, Mistral AI, pgvector, fastembed, APScheduler, python-jobspy |
| Base de données | PostgreSQL 16 (AWS RDS) — 2 bases : métier + vectorielle (pgvector) |
| Infrastructure | AWS (VPC, EC2, RDS, S3, CloudFront), Terraform |
| CI/CD | GitHub Actions (tests, build, deploy — 3 images Docker) |
| Conteneurisation | Docker, Docker Compose, Docker Hub |
| Emails | Spring Mail + Brevo (SMTP), Mailpit en dev |
| Tests | JUnit 5 + JaCoCo (backend), Jest + RTL (frontend) — coverage > 80% |
Internet
│
Elastic IP (13.62.12.231)
│
┌─────────── VPC 10.0.0.0/16 ───────────┐
│ │
│ ┌─ Subnet public A ─────────────────┐ │
│ │ EC2 t3.small (Docker Compose) │ │
│ │ ├─ Frontend Next.js :3000 │ │
│ │ ├─ Backend Spring Boot :8080 │ │
│ │ └─ Agent IA FastAPI :8002 │ │
│ └───────────────────────────────────┘ │
│ │ port 5432 │
│ ▼ │
│ ┌─ Subnet privé A ──────────────────┐ │
│ │ RDS PostgreSQL 16 │ │
│ │ ├─ candidature (métier) │ │
│ │ └─ akazi_agent (pgvector) │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────┘
│
Brevo (SMTP) → emails de vérification
Points clés :
- La base de données est isolée dans un subnet privé, accessible uniquement depuis le backend
- Les security groups segmentent les accès : le SG de la BDD n'accepte que le SG du backend
- L'Elastic IP garantit une adresse fixe même après redémarrage de l'instance
- Le state Terraform est stocké dans un bucket S3 distant avec verrouillage
- Procédure de bootstrap d'une nouvelle instance : voir
docs/DEPLOY.md
L'agent est un microservice FastAPI orchestré par LangGraph : un nœud gardien classifie l'intention (offres, profil, CV, entretien, hors-sujet) et route vers le bon pipeline. Le matching offres ↔ profil repose sur des embeddings (fastembed, BAAI/bge-small) stockés dans pgvector avec recherche par similarité cosinus.
Deux modes :
- Conversationnel — chat intégré au dashboard (drawer) : recherche d'offres en langage naturel avec suivi de contexte, conseils CV et préparation d'entretien ancrés sur des offres réelles (garde-fous : jamais d'expérience inventée)
- Autonome — un scheduler (tick horaire) exécute pour chaque utilisateur, à la fréquence qu'il a choisie (6h à 7 jours) : scraping ciblé (métier/lieu/séniorité extraits par LLM) → matching sémantique → création des candidatures dans son dashboard (
source=AGENT, badge « Assistant »)
Sécurité — délégation par utilisateur :
Utilisateur Backend Agent (service)
───────────────────────────────────────────────────────────────────────
active l'assistant ──► crée un grant (révocable)
+ recherches + fréquence
GET /assistant/grants ◄── secret de service
renvoie, par grant actif,
un JWT scopé (actor=AGENT)
──► agit pour chaque user
avec SON token
désactive ──► grant inactif → plus de token émis
- L'agent ne détient aucun mot de passe utilisateur ; chaque token est scopé à un seul utilisateur et révocable indépendamment
- Le backend force
source=AGENTcôté serveur (claimactordu JWT →ROLE_AGENT) - Les endpoints
/chatet/profilede l'agent valident le JWT Akazi (profil par utilisateur)
- Authentification sécurisée (inscription, connexion, token JWT)
- CRUD complet : candidatures et notes
- Isolation des données par utilisateur (chaque user ne voit que ses propres candidatures)
- Suivi par statut : À postuler · Postulé · Entretien · Offre · Refus
- Priorités : Haute · Moyenne · Basse
- Statistiques par utilisateur (taux de réponse, répartition par statut)
- Validation des données entrantes avec messages d'erreur en français
- DTOs et mappers pour sécuriser les réponses API (aucune donnée sensible exposée)
- Gestion d'erreurs centralisée (GlobalExceptionHandler)
src/main/java/com/emploi/candidature/
├── configuration/ # SecurityConfig, JwtFilter, SwaggerConfig
├── controller/ # AuthController, CandidatureController, NoteController, UserController
├── dto/ # Request & Response DTOs
├── entity/ # User, Candidature, Note, Statut, Priorite
├── exception/ # GlobalExceptionHandler
├── mapper/ # UserMapper, CandidatureMapper, NoteMapper
├── repository/ # JPA Repositories
└── service/ # UserService, CandidatureService, NoteService, JwtService
- Java 21+
- Docker & Docker Compose
- Maven (ou utiliser le wrapper
./mvnw)
1. Cloner le projet
git clone https://github.com/NtwariGB/Candidature.git
cd Candidature2. Lancer la base de données
docker compose up -d3. Configurer les variables d'environnement
Copier le fichier d'exemple et adapter :
cp application.properties.example src/main/resources/application.properties4. Lancer l'application
# Lancer PostgreSQL
docker compose up postgres -d
# Lancer le backend
./mvnw spring-boot:run5. Accéder à Swagger
http://localhost:8080/swagger-ui.html
Inscription
docker compose up --build -dBackend :
curl -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@test.com","password":"123456"}'Frontend :
curl -X POST http://localhost:8080/api/candidatures \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <votre_token>" \
-d '{"entreprise":"Doctolib","poste":"Cloud Engineer","salaire":"62K€","statut":"POSTULE","priorite":"HAUTE"}'| Méthode | Route | Description |
|---|---|---|
| POST | /api/auth/register |
Inscription |
| POST | /api/auth/login |
Connexion |
| GET | /api/users/me |
Mon profil |
| GET | /api/candidatures |
Mes candidatures |
| POST | /api/candidatures |
Créer une candidature |
| GET | /api/candidatures/{id} |
Détail d'une candidature |
| PUT | /api/candidatures/{id} |
Modifier une candidature |
| PATCH | /api/candidatures/{id}/statut |
Changer le statut |
| DELETE | /api/candidatures/{id} |
Supprimer une candidature |
| GET | /api/candidatures/stats |
Mes statistiques |
| GET | /api/candidatures/statut/{statut} |
Filtrer par statut |
| POST | /api/notes/candidature/{id} |
Ajouter une note |
| GET | /api/notes/candidature/{id} |
Notes d'une candidature |
Guy Béni Ntwari — GitHub