Student mobility platform built as a microservices lab project. Polymove helps students discover internship opportunities enriched with local context: city signals, news, recommendations, notifications, and messaging-driven integrations.
Polymove exposes a single entrypoint for students:
- explore internship offers
- get recommended offers based on their domain
- receive offer notifications
- view offers enriched with MI8 city signals and recent news
The project also demonstrates event-driven messaging with RabbitMQ across the services.
- polytech: API gateway and orchestrator. Stores students and notifications in PostgreSQL, exposes the main HTTP API, aggregates offers from Erasmumu and city signals from MI8, and publishes/consumes messaging events.
- erasmumu: internship offer service. Stores offers in MongoDB and exposes CRUD plus filtered listing.
- mi8: city intelligence service. Stores news in Redis, exposes gRPC methods for latest news and city scores, and consumes
news.createdevents. - laposte: subscriber and alert-preferences service. Stores subscriber preferences in MongoDB and reacts to student and offer events.
- frontend: React + TypeScript + Vite frontend, served via Nginx in Docker.
- colporteur: demo publisher that injects random city news into RabbitMQ.
- seeder: optional Rust seed service that creates a larger offer dataset through the public API, using the versioned fixture file
services/seeder/data/offers.json.
- PostgreSQL: students and notifications
- MongoDB: offers and La Poste subscribers
- Redis: MI8 news timelines and scores
- RabbitMQ: event bus
Polymove currently demonstrates these main flows:
student.registeredPolytech publishes when a student is created, La Poste auto-creates a subscriber profile.offer.createdErasmumu publishes when a new offer is created, Polytech creates notifications and La Poste can react for alerts.news.createdColporteur publishes city news, MI8 consumes and updates latest news and city scores.
- Rust microservices
- React + TypeScript + Vite frontend
- shadcn/ui + Tailwind CSS
- PostgreSQL, MongoDB, Redis
- RabbitMQ
- Docker Compose
.
├── frontend/ # current React frontend
├── proto/ # shared protobuf definitions
├── services/
│ ├── colporteur/
│ ├── erasmumu/
│ ├── laposte/
│ ├── mi8/
│ ├── polytech/
│ └── seeder/
└── docker-compose.yml
Create a local .env from the example:
cp .env.example .envdocker compose up --buildOptional seeded startup:
docker compose --profile seed up --buildThis keeps the normal startup unchanged. The seed profile only adds the one-shot seeder service that creates offers.
- Frontend:
http://localhost:5173 - Polytech API:
http://localhost:3000 - Erasmumu API:
http://localhost:3001 - La Poste API:
http://localhost:3002 - RabbitMQ management:
http://localhost:15672
seed: creates a larger set of offers fromservices/seeder/data/offers.jsontools: enables one-shot tools such ascolporteur
Example with demo data and news:
docker compose --profile seed --profile tools up --build
docker compose --profile tools run --rm colporteurImportant:
seedonly creates offerscolporteuris what feedsmi8with city news- without
colporteur, offer cards are expected to stay flat in the UI:match score = 0, no city metrics, and no recent signal
- Either create a student and offers manually with the API flow below, or start the stack with
--profile seedTheseedprofile only creates offers for the Explorer. The Dashboard still needs a student created through the API. - Open
http://localhost:5173 - Go to
Explorer - Search with at least one filter such as:
city = Parisdomain = AIIf you only started with--profile seed, the cards will load but remain unenriched until you runcolporteur
- Go to
Dashboard - Paste a valid
Student ID - Verify:
RecommendationsApplicationsNotifications
Create a student for the Dashboard:
curl -s -X POST http://localhost:3000/student \
-H 'Content-Type: application/json' \
-d '{
"firstname": "John",
"name": "Doe",
"domain": "AI"
}'Check subscriber creation:
curl -s http://localhost:3002/subscribers/<student-id>Create an offer manually if you are not using --profile seed:
curl -s -X POST http://localhost:3001/offer \
-H 'Content-Type: application/json' \
-d '{
"title": "AI Mobility Analyst",
"link": "https://example.com/offer/ai-mobility-analyst",
"city": "Paris",
"domain": "AI",
"salary": 1450,
"start_date": "2026-04-15",
"end_date": "2026-09-30"
}'Create more offers if you want to test sorting and pagination:
curl -s -X POST http://localhost:3001/offer \
-H 'Content-Type: application/json' \
-d '{
"title": "Computer Vision Intern",
"link": "https://example.com/offer/computer-vision-intern",
"city": "Lyon",
"domain": "AI",
"salary": 1550,
"start_date": "2026-05-01",
"end_date": "2026-10-31"
}'
curl -s -X POST http://localhost:3001/offer \
-H 'Content-Type: application/json' \
-d '{
"title": "Urban Data Intern",
"link": "https://example.com/offer/urban-data-intern",
"city": "Nice",
"domain": "AI",
"salary": 1350,
"start_date": "2026-06-01",
"end_date": "2026-11-30"
}'Check notifications:
curl -s http://localhost:3000/students/<student-id>/notificationsPublish MI8 demo news:
docker compose --profile tools run --rm colporteurThis step is what makes the UI interesting:
- Explorer cards get non-zero match scores
- city metrics appear
- recent signals appear
- dashboard recommendations become differentiated by city context
Check enriched offers:
curl -s "http://localhost:3000/offers?city=Paris&limit=10"
curl -s "http://localhost:3000/students/<student-id>/recommended-offers?limit=5&sort_by=safety"- The current backend requires at least one filter for
GET /offers, so the Explorer starts from a filtered search flow. - The frontend only talks to the gateway and La Poste through same-origin proxy routes in Docker.
- The
seedprofile is optional and keeps the default startup unchanged. - If you want a clean demo, reset volumes first:
docker compose down -v
docker compose up --buildFrontend only:
cd frontend
npm i
npm run devFull stack rebuild:
docker compose up --build