diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/.gitignore b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/.gitignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/.gitignore
@@ -0,0 +1 @@
+node_modules
diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/servicemicrobloggr.png b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/doc/servicemicrobloggr.png
similarity index 100%
rename from cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/servicemicrobloggr.png
rename to cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/doc/servicemicrobloggr.png
diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/docker-compose.yml b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/docker-compose.yml
new file mode 100644
index 0000000..f06faff
--- /dev/null
+++ b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/docker-compose.yml
@@ -0,0 +1,14 @@
+version: '2'
+services:
+ gateway:
+ build:
+ context: containers/gateway
+ ports:
+ - 8080:80
+
+ home_page:
+ build:
+ context: containers/home_page
+ login_page:
+ build:
+ context: containers/login_page
diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/gateway/Dockerfile b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/gateway/Dockerfile
new file mode 100644
index 0000000..997772f
--- /dev/null
+++ b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/gateway/Dockerfile
@@ -0,0 +1,8 @@
+FROM alpine:3.2
+
+RUN apk add --no-cache nginx
+COPY microbloggr.conf /etc/nginx/microbloggr.conf
+
+RUN mkdir -p /var/run/nginx
+
+CMD ["nginx", "-c", "/etc/nginx/microbloggr.conf"]
diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/gateway/microbloggr.conf b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/gateway/microbloggr.conf
new file mode 100644
index 0000000..f289811
--- /dev/null
+++ b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/gateway/microbloggr.conf
@@ -0,0 +1,45 @@
+daemon off;
+
+worker_processes 1;
+
+events {
+ worker_connections 1024;
+}
+
+http {
+ include mime.types;
+ default_type application/octet-stream;
+
+ sendfile on;
+ keepalive_timeout 65;
+
+ gzip on;
+
+ server {
+
+ listen 80;
+ server_name localhost;
+
+ location / {
+ proxy_pass http://home_page:8080/;
+ }
+
+ location /login {
+ proxy_pass http://login_page:8080/;
+ }
+
+ # location /profile {
+ # proxy_pass http://profile_page:8080/;
+ # }
+ #
+ # location /register {
+ # proxy_pass http://register_page:8080/;
+ # }
+ #
+ # location /statuses {
+ # proxy_pass http://statuses_page:8080/;
+ # }
+
+ }
+
+}
diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/home_page/.dockerignore b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/home_page/.dockerignore
new file mode 100644
index 0000000..4e4a23e
--- /dev/null
+++ b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/home_page/.dockerignore
@@ -0,0 +1 @@
+/app/node_modules
diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/home_page/Dockerfile b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/home_page/Dockerfile
new file mode 100644
index 0000000..9cdaec8
--- /dev/null
+++ b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/home_page/Dockerfile
@@ -0,0 +1,10 @@
+FROM alpine:3.7
+
+RUN apk add --no-cache nodejs
+
+COPY app /app
+WORKDIR /app
+
+RUN npm install --production
+
+CMD ["node", "server.js"]
diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/home_page/app/.gitignore b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/home_page/app/.gitignore
new file mode 100644
index 0000000..07e6e47
--- /dev/null
+++ b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/home_page/app/.gitignore
@@ -0,0 +1 @@
+/node_modules
diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/home_page/app/package.json b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/home_page/app/package.json
new file mode 100644
index 0000000..85dc18f
--- /dev/null
+++ b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/home_page/app/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "login_page",
+ "version": "1.0.0",
+ "description": "",
+ "main": "server.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "ejs": "^2.5.7",
+ "express": "^4.16.2",
+ "express-session": "^1.15.6",
+ "request": "^2.83.0",
+ "request-promise-native": "^1.0.5"
+ }
+}
diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/home_page/app/server.js b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/home_page/app/server.js
new file mode 100644
index 0000000..66d6580
--- /dev/null
+++ b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/home_page/app/server.js
@@ -0,0 +1,31 @@
+const express = require('express');
+const session = require('express-session');
+const app = express();
+
+app.set('view engine', 'ejs');
+
+app.use(session({
+ secret: 'AbsolutlyNotSecret', // Ce secret doit être partagé par tous les microservices *_page
+ resave: false,
+ saveUninitialized: true,
+}));
+
+// GET /
+// Retourne la page d'accueil de MicroBloggr ou redirige vers la
+// page de login si l'utilisateur n'est pas identifié
+app.get('/', (req, res) => {
+
+ const sess = req.session;
+
+ // On vérifie si un utilisateur a été associé à la session HTTP
+ if (req.session.user) {
+ // Si oui, on affichage la page d'accueil
+ return res.render('index');
+ } else {
+ // Si non, on redirige l'utilisateur vers la page de login
+ return res.redirect(301, '/login');
+ }
+
+});
+
+app.listen(8080, () => console.log('listening on port 8080'));
diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/home_page/app/views/index.ejs b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/home_page/app/views/index.ejs
new file mode 100644
index 0000000..2bb1ef3
--- /dev/null
+++ b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/home_page/app/views/index.ejs
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/login_page/.dockerignore b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/login_page/.dockerignore
new file mode 100644
index 0000000..4e4a23e
--- /dev/null
+++ b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/login_page/.dockerignore
@@ -0,0 +1 @@
+/app/node_modules
diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/login_page/Dockerfile b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/login_page/Dockerfile
new file mode 100644
index 0000000..9cdaec8
--- /dev/null
+++ b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/login_page/Dockerfile
@@ -0,0 +1,10 @@
+FROM alpine:3.7
+
+RUN apk add --no-cache nodejs
+
+COPY app /app
+WORKDIR /app
+
+RUN npm install --production
+
+CMD ["node", "server.js"]
diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/login_page/app/.gitignore b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/login_page/app/.gitignore
new file mode 100644
index 0000000..07e6e47
--- /dev/null
+++ b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/login_page/app/.gitignore
@@ -0,0 +1 @@
+/node_modules
diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/login_page/app/package.json b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/login_page/app/package.json
new file mode 100644
index 0000000..954d211
--- /dev/null
+++ b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/login_page/app/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "login_page",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "body-parser": "^1.18.2",
+ "ejs": "^2.5.7",
+ "express": "^4.16.2",
+ "express-session": "^1.15.6",
+ "request": "^2.83.0",
+ "request-promise-native": "^1.0.5"
+ }
+}
diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/login_page/app/server.js b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/login_page/app/server.js
new file mode 100644
index 0000000..d929560
--- /dev/null
+++ b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/login_page/app/server.js
@@ -0,0 +1,91 @@
+const express = require('express');
+const app = express();
+const session = require('express-session');
+const bodyParser = require('body-parser');
+const request = require('request-promise-native');
+
+app.set('view engine', 'ejs');
+
+app.use(session({
+ secret: 'AbsolutlyNotSecret', // Ce secret doit être partagé par tous les microservices *_page
+ resave: false,
+ saveUninitialized: true,
+}));
+
+// On utilise le module "body-parser"
+// pour faciliter la désérialisation des données
+// du formulaire
+app.use(bodyParser.urlencoded({extended: false}));
+
+// GET /
+// Affiche la page de login
+app.get('/', (req, res) => {
+ res.render('index', { error: null });
+});
+
+
+// POST /
+// Récupère les données du formulaire de login
+app.post('/', (req, res) => {
+
+ const form = req.body;
+
+ // Si le formulaire ne contient pas les données attendues
+ // on renvoie un code HTTP 400 pour indiquer une requête invalide.
+ if(!form.username || !form.password) {
+ return res.render('index', { error: 'Identifiant ou mot de passe manquant.'});
+ }
+
+ // On interroge le microservices "users"
+ // pour récupérer l'utilisateur associé à l'identifiant
+ // fourni dans le formulaire
+ findUserByName(form.username)
+ .then(user => {
+
+ // Si l'utilisateur n'existe pas, on affiche un message d'erreur
+ if(!user) {
+ return res.render('index', { error: 'Identifiant invalide !'});
+ }
+
+ // Si le mot de passe de l'utilisateur correspond à celui enregistré sur le
+ // microservice "users", on attache l'identifiant de l'utilisateur à la session
+ // HTTP puis on le redirige sur la page d'accueil
+ if(user.password === form.password) {
+ req.session.user = user;
+ return res.redirect(302, '/');
+ } else {
+ // Sinon, on affiche un message d'erreur
+ return res.render('index', { error: 'Mot de passe invalide !'});
+ }
+
+ })
+ .catch(err => {
+ // Une erreur s'est produite en essayant de contacter le microservices "users"
+ // On affiche un message d'erreur à l'utilisateur
+ return res.render('index', {
+ error: 'Une erreur s\'est produite pendant le processus d\'authentification.'
+ });
+ })
+ ;
+
+});
+
+app.listen(8080, () => console.log('listening on port 8080'));
+
+// Récupère un utilisateur via son identifiant
+// sur le microservice "users"
+function findUserByName(username) {
+ return request({
+ uri: `http://users:8080/users/${username}`,
+ json: true
+ })
+ .catch(err => {
+ // Si le microservice "users" répond avec un code HTTP 404
+ // c'est que l'utilisateur n'existe pas
+ if (err.response.statusCode === 404) {
+ return null;
+ }
+ throw err;
+ })
+ ;
+}
diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/login_page/app/views/index.ejs b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/login_page/app/views/index.ejs
new file mode 100644
index 0000000..67efbca
--- /dev/null
+++ b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/login_page/app/views/index.ejs
@@ -0,0 +1,19 @@
+
+
+
Login
+ <% if (error) { %>
+
<%= error %>
+ <% } %>
+
+
+
diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/sessions/.dockerignore b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/sessions/.dockerignore
new file mode 100644
index 0000000..4e4a23e
--- /dev/null
+++ b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/sessions/.dockerignore
@@ -0,0 +1 @@
+/app/node_modules
diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/sessions/Dockerfile b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/sessions/Dockerfile
new file mode 100644
index 0000000..9cdaec8
--- /dev/null
+++ b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/sessions/Dockerfile
@@ -0,0 +1,10 @@
+FROM alpine:3.7
+
+RUN apk add --no-cache nodejs
+
+COPY app /app
+WORKDIR /app
+
+RUN npm install --production
+
+CMD ["node", "server.js"]
diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/sessions/app/package.json b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/sessions/app/package.json
new file mode 100644
index 0000000..dfb2c2e
--- /dev/null
+++ b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/sessions/app/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "sessions",
+ "version": "1.0.0",
+ "description": "",
+ "main": "server.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "body-parser": "^1.18.2",
+ "express": "^4.16.2"
+ }
+}
diff --git a/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/sessions/app/server.js b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/sessions/app/server.js
new file mode 100644
index 0000000..b19bb11
--- /dev/null
+++ b/cesi/architecture_n_tiers/ressources/exercices/ex_microbloggr_solution/services/sessions/app/server.js
@@ -0,0 +1,102 @@
+const express = require('express');
+const bodyParser = require('body-parser');
+const app = express();
+
+const sessions = {};
+let sessionCounter = 0;
+
+// On utilise le middleware "body-parser"
+// pour désérialiser automatiquement
+// les objets JSON potentiellement
+// fournis par les requêtes
+app.use(bodyParser.json());
+
+// GET /sessions
+// Permet de récupérer la liste des sessions en cours
+app.get('/sessions', (req, res) => {
+ res.json(sessions);
+});
+
+
+// GET /sessions/:id
+// Permet de récupérer une session via son identifiant
+app.get('/sessions/:id', (req, res) => {
+
+ // On récupère l'identifiant de session
+ // depuis l'URL
+ const sessionId = req.params.id;
+
+ // On vérifie que la session existe.
+ // Si ce n'est pas le cas, on retourne un code
+ // HTTP 404 pour indiquer que la ressource
+ // n'existe pas
+ if (!sessions.hasOwnProperty(sessionId)) {
+ return res.status(404).end('Not found.');
+ }
+
+ // On retourne la session sérialisée en JSON
+ return res.json(sessions[sessionId]);
+
+})
+
+// POST /sessions
+// Body: {"user": }
+// Permet de créer une nouvelle session pour l'utilisateur donné
+app.post('/sessions', (req, res) => {
+
+ const payload = req.body;
+
+ // On valide la présence de l'attribut "user"
+ // sur les données fournies.
+ // Si il n'est pas présent, on retourne un code HTTP
+ // 400 pour indiquer que la requête est invalide.
+ if (!payload.user) {
+ return res.status(400).end('Bad request.');
+ }
+
+ // On créait une nouvelle session
+ const sess = {
+ user: payload.user,
+ id: getSessionId(),
+ };
+
+ // On la "stocke" dans notre registre
+ sessions[session.id] = sess;
+
+ // On retourne la session
+ // nouvellement créée
+ res.json(sess);
+
+})
+
+
+// DELETE /sessions/:id
+// Permet de supprimer une session dans le registre via son identifiant
+app.delete('/sessions/:id', (req, res) => {
+
+ // On récupère l'identifiant de session
+ // depuis l'URL
+ const sessionId = req.params.id;
+
+ // On vérifie que la session existe.
+ // Si ce n'est pas le cas, on retourne un code
+ // HTTP 404 pour indiquer que la ressource
+ // n'existe pas
+ if (!sessions.hasOwnProperty(sessionId)) {
+ return res.status(404).end('Not found.');
+ }
+
+ delete sessions[sessionId];
+
+ // On retourne un code HTTP 204 pour indiquer que l'opération
+ // s'est déroulée correctement mais qu'aucun "contenu" n'est disponible
+ return res.status(204);
+
+});
+
+
+app.listen(8080, () => console.log('listening on port 8080'));
+
+function getSessionId() {
+ return `${Date.now()}-${sessionCounter++}`;
+}
diff --git a/cesi/architecture_n_tiers/ressources/exercices/exo/ex_2_tiers.zip b/cesi/architecture_n_tiers/ressources/exercices/exo/ex_2_tiers.zip
new file mode 100644
index 0000000..9465916
Binary files /dev/null and b/cesi/architecture_n_tiers/ressources/exercices/exo/ex_2_tiers.zip differ
diff --git a/cesi/architecture_n_tiers/ressources/exercices/exo/ex_2_tiers_solution.zip b/cesi/architecture_n_tiers/ressources/exercices/exo/ex_2_tiers_solution.zip
new file mode 100644
index 0000000..b264f09
Binary files /dev/null and b/cesi/architecture_n_tiers/ressources/exercices/exo/ex_2_tiers_solution.zip differ
diff --git a/cesi/architecture_n_tiers/ressources/exercices/exo/ex_microbloggr.zip b/cesi/architecture_n_tiers/ressources/exercices/exo/ex_microbloggr.zip
new file mode 100644
index 0000000..81e6160
Binary files /dev/null and b/cesi/architecture_n_tiers/ressources/exercices/exo/ex_microbloggr.zip differ
diff --git a/cesi/architecture_n_tiers/ressources/exercices/exo/servicemicrobloggr.png b/cesi/architecture_n_tiers/ressources/exercices/exo/servicemicrobloggr.png
new file mode 100644
index 0000000..7672225
Binary files /dev/null and b/cesi/architecture_n_tiers/ressources/exercices/exo/servicemicrobloggr.png differ
diff --git a/cesi/architecture_n_tiers/ressources/schemas_slides.epgz b/cesi/architecture_n_tiers/ressources/schemas_slides.epgz
index 5439b7d..e5d3b34 100644
Binary files a/cesi/architecture_n_tiers/ressources/schemas_slides.epgz and b/cesi/architecture_n_tiers/ressources/schemas_slides.epgz differ