diff --git a/apache/site.conf b/apache/site.conf
new file mode 100644
index 0000000..cdee7c7
--- /dev/null
+++ b/apache/site.conf
@@ -0,0 +1,18 @@
+
+ ServerName app.local
+
+ SSLEngine on
+ SSLCertificateFile "/etc/apache2/ssl/cert.pem"
+ SSLCertificateKeyFile "/etc/apache2/ssl/key.pem"
+
+ ProxyPreserveHost On
+
+ # API Publique
+ ProxyPass /api/pub/ http://localhost:5001/
+ ProxyPassReverse /api/pub/ http://localhost:5001/
+
+ # API Utilisateur
+ ProxyPass /api/user/ http://localhost:5002/
+ ProxyPassReverse /api/user/ http://localhost:5002/
+
+
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..dd76bfc
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,51 @@
+version: '3.8'
+
+services:
+ keycloak-db:
+ image: postgres:15
+ environment:
+ POSTGRES_DB: keycloak
+ POSTGRES_USER: keycloak
+ POSTGRES_PASSWORD: password
+ volumes:
+ - keycloak-db:/var/lib/postgresql/data
+
+ keycloak:
+ image: quay.io/keycloak/keycloak:24.0.3
+ command: start-dev
+ environment:
+ KC_DB: postgres
+ KC_DB_URL_HOST: keycloak-db
+ KC_DB_URL_DATABASE: keycloak
+ KC_DB_USERNAME: keycloak
+ KC_DB_PASSWORD: password
+ KEYCLOAK_ADMIN: admin
+ KEYCLOAK_ADMIN_PASSWORD: admin
+ ports:
+ - "8080:8080"
+ depends_on:
+ - keycloak-db
+
+ public_api:
+ build:
+ context: ./public
+ volumes:
+ - ./db.sqlite:/app/db.sqlite
+ ports:
+ - "5001:5001"
+ depends_on:
+ - keycloak
+
+ user_api:
+ build:
+ context: ./private
+ volumes:
+ - ./db.sqlite:/app/db.sqlite
+ ports:
+ - "5002:5002"
+ depends_on:
+ - keycloak
+
+volumes:
+ keycloak-db:
+
diff --git a/keyclock-setup.sh b/keyclock-setup.sh
new file mode 100755
index 0000000..54767d7
--- /dev/null
+++ b/keyclock-setup.sh
@@ -0,0 +1,151 @@
+#!/bin/bash
+
+# Variables
+KC_HOST="http://localhost:8080"
+REALM="myrealm"
+CLIENT_ID="myclient"
+CLIENT_SECRET="mysecret"
+USERNAME="alexis"
+PASSWORD="password"
+
+# Fonction d'attente
+wait_for_keycloak() {
+ echo "⏳ Attente de Keycloak..."
+ until curl -s "$KC_HOST" > /dev/null; do
+ sleep 2
+ done
+ echo "✅ Keycloak est prêt."
+}
+
+# Obtenir un token admin
+get_admin_token() {
+ curl -s -X POST "$KC_HOST/realms/master/protocol/openid-connect/token" \
+ -H "Content-Type: application/x-www-form-urlencoded" \
+ -d "username=admin" \
+ -d "password=admin" \
+ -d "grant_type=password" \
+ -d "client_id=admin-cli" |
+ jq -r .access_token
+}
+
+# Créer un realm, client et utilisateur
+setup_keycloak() {
+ TOKEN=$(get_admin_token)
+
+ echo "🛠️ Création du realm $REALM..."
+ curl -s -X POST "$KC_HOST/admin/realms" \
+ -H "Authorization: Bearer $TOKEN" \
+ -H "Content-Type: application/json" \
+ -d "{\"realm\":\"$REALM\",\"enabled\":true}" > /dev/null
+
+ echo "🛠️ Création du client $CLIENT_ID..."
+ curl -s -X POST "$KC_HOST/admin/realms/$REALM/clients" \
+ -H "Authorization: Bearer $TOKEN" \
+ -H "Content-Type: application/json" \
+ -d "{
+ \"clientId\": \"$CLIENT_ID\",
+ \"enabled\": true,
+ \"publicClient\": false,
+ \"secret\": \"$CLIENT_SECRET\",
+ \"redirectUris\": [\"*\"],
+ \"standardFlowEnabled\": true
+ }" > /dev/null
+
+ echo "👤 Création de l'utilisateur $USERNAME..."
+ curl -s -X POST "$KC_HOST/admin/realms/$REALM/users" \
+ -H "Authorization: Bearer $TOKEN" \
+ -H "Content-Type: application/json" \
+ -d "{
+ \"username\": \"$USERNAME\",
+ \"enabled\": true,
+ \"credentials\": [{
+ \"type\": \"password\",
+ \"value\": \"$PASSWORD\",
+ \"temporary\": false
+ }]
+ }" > /dev/null
+
+ echo "✅ Configuration terminée !"
+ echo "🔐 Utilisateur: $USERNAME / $PASSWORD"
+ echo "🪪 Client secret: $CLIENT_SECRET"
+}
+
+# Lancer le setup
+wait_for_keycloak
+setup_keycloak
+#!/bin/bash
+
+# Variables
+KC_HOST="http://localhost:8080"
+REALM="myrealm"
+CLIENT_ID="myclient"
+CLIENT_SECRET="mysecret"
+USERNAME="alexis"
+PASSWORD="password"
+
+# Fonction d'attente
+wait_for_keycloak() {
+ echo "⏳ Attente de Keycloak..."
+ until curl -s "$KC_HOST" > /dev/null; do
+ sleep 2
+ done
+ echo "✅ Keycloak est prêt."
+}
+
+# Obtenir un token admin
+get_admin_token() {
+ curl -s -X POST "$KC_HOST/realms/master/protocol/openid-connect/token" \
+ -H "Content-Type: application/x-www-form-urlencoded" \
+ -d "username=admin" \
+ -d "password=admin" \
+ -d "grant_type=password" \
+ -d "client_id=admin-cli" |
+ jq -r .access_token
+}
+
+# Créer un realm, client et utilisateur
+setup_keycloak() {
+ TOKEN=$(get_admin_token)
+
+ echo "🛠️ Création du realm $REALM..."
+ curl -s -X POST "$KC_HOST/admin/realms" \
+ -H "Authorization: Bearer $TOKEN" \
+ -H "Content-Type: application/json" \
+ -d "{\"realm\":\"$REALM\",\"enabled\":true}" > /dev/null
+
+ echo "🛠️ Création du client $CLIENT_ID..."
+ curl -s -X POST "$KC_HOST/admin/realms/$REALM/clients" \
+ -H "Authorization: Bearer $TOKEN" \
+ -H "Content-Type: application/json" \
+ -d "{
+ \"clientId\": \"$CLIENT_ID\",
+ \"enabled\": true,
+ \"publicClient\": false,
+ \"secret\": \"$CLIENT_SECRET\",
+ \"redirectUris\": [\"*\"],
+ \"standardFlowEnabled\": true
+ }" > /dev/null
+
+ echo "👤 Création de l'utilisateur $USERNAME..."
+ curl -s -X POST "$KC_HOST/admin/realms/$REALM/users" \
+ -H "Authorization: Bearer $TOKEN" \
+ -H "Content-Type: application/json" \
+ -d "{
+ \"username\": \"$USERNAME\",
+ \"enabled\": true,
+ \"credentials\": [{
+ \"type\": \"password\",
+ \"value\": \"$PASSWORD\",
+ \"temporary\": false
+ }]
+ }" > /dev/null
+
+ echo "✅ Configuration terminée !"
+ echo "🔐 Utilisateur: $USERNAME / $PASSWORD"
+ echo "🪪 Client secret: $CLIENT_SECRET"
+}
+
+# Lancer le setup
+wait_for_keycloak
+setup_keycloak
+
diff --git a/private/Dockerfile b/private/Dockerfile
new file mode 100644
index 0000000..27e4f76
--- /dev/null
+++ b/private/Dockerfile
@@ -0,0 +1,9 @@
+FROM python:3.11-slim
+
+WORKDIR /app
+COPY . .
+
+RUN pip install flask flask_sqlalchemy pyjwt requests
+
+CMD ["python", "app.py"]
+
diff --git a/private/app.py b/private/app.py
new file mode 100644
index 0000000..b7d1d5c
--- /dev/null
+++ b/private/app.py
@@ -0,0 +1,83 @@
+--- user_api/app.py ---
+from flask import Flask, jsonify, request, abort
+from flask_sqlalchemy import SQLAlchemy
+import requests
+import jwt
+
+app = Flask(__name__)
+app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///../db.sqlite'
+app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
+
+db = SQLAlchemy(app)
+
+# Configuration Keycloak
+KEYCLOAK_URL = "http://localhost:8080"
+REALM = "myrealm"
+CLIENT_ID = "myclient"
+
+class Visite(db.Model):
+ id = db.Column(db.Integer, primary_key=True)
+ galerie_id = db.Column(db.Integer, nullable=False)
+
+class Critique(db.Model):
+ id = db.Column(db.Integer, primary_key=True)
+ oeuvre_id = db.Column(db.Integer, nullable=False)
+ texte = db.Column(db.Text, nullable=False)
+ username = db.Column(db.String(100), nullable=False)
+
+def verify_token(auth_header):
+ if not auth_header.startswith("Bearer "):
+ return None
+ token = auth_header.split(" ")[1]
+ try:
+ jwks_url = f"{KEYCLOAK_URL}/realms/{REALM}/protocol/openid-connect/certs"
+ jwks = requests.get(jwks_url).json()
+ public_keys = {k['kid']: jwt.algorithms.RSAAlgorithm.from_jwk(k) for k in jwks['keys']}
+ unverified = jwt.decode(token, options={"verify_signature": False})
+ kid = unverified['kid']
+ decoded = jwt.decode(token, key=public_keys[kid], algorithms=['RS256'], audience=CLIENT_ID)
+ return decoded['preferred_username']
+ except Exception as e:
+ print(f"JWT verification failed: {e}")
+ return None
+
+@app.before_request
+def authenticate():
+ auth = request.headers.get("Authorization", "")
+ user = verify_token(auth)
+ if not user:
+ return jsonify({"error": "Unauthorized"}), 403
+ request.user = user
+
+@app.route("/", methods=["GET"])
+def index():
+ return f"User API - Authenticated as {request.user}", 200
+
+@app.route("/galerie//entrer", methods=["POST"])
+def entrer_galerie(galerie_id):
+ visite = Visite(galerie_id=galerie_id)
+ db.session.add(visite)
+ db.session.commit()
+ return jsonify({"message": "Entré dans la galerie"}), 201
+
+@app.route("/galerie//sortir", methods=["POST"])
+def sortir_galerie(galerie_id):
+ Visite.query.filter_by(galerie_id=galerie_id).delete()
+ db.session.commit()
+ return jsonify({"message": "Sorti de la galerie"}), 200
+
+@app.route("/oeuvre//critiquer", methods=["POST"])
+def critiquer_oeuvre(oeuvre_id):
+ data = request.get_json()
+ if not data or not data.get("texte"):
+ abort(400)
+ critique = Critique(oeuvre_id=oeuvre_id, texte=data["texte"], username=request.user)
+ db.session.add(critique)
+ db.session.commit()
+ return jsonify({"message": "Critique ajoutée"}), 201
+
+if __name__ == "__main__":
+ with app.app_context():
+ db.create_all()
+ app.run(port=5002)
+
diff --git a/public/Dockerfile b/public/Dockerfile
new file mode 100644
index 0000000..27e4f76
--- /dev/null
+++ b/public/Dockerfile
@@ -0,0 +1,9 @@
+FROM python:3.11-slim
+
+WORKDIR /app
+COPY . .
+
+RUN pip install flask flask_sqlalchemy pyjwt requests
+
+CMD ["python", "app.py"]
+
diff --git a/public/app.py b/public/app.py
new file mode 100644
index 0000000..bb1b59d
--- /dev/null
+++ b/public/app.py
@@ -0,0 +1,47 @@
+--- public_api/app.py ---
+from flask import Flask, jsonify
+from flask_sqlalchemy import SQLAlchemy
+
+app = Flask(__name__)
+app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///../db.sqlite'
+app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
+
+db = SQLAlchemy(app)
+
+class Artiste(db.Model):
+ id = db.Column(db.Integer, primary_key=True)
+ nom = db.Column(db.String(100), nullable=False)
+
+class Galerie(db.Model):
+ id = db.Column(db.Integer, primary_key=True)
+ nom = db.Column(db.String(100), nullable=False)
+
+class Oeuvre(db.Model):
+ id = db.Column(db.Integer, primary_key=True)
+ titre = db.Column(db.String(200), nullable=False)
+ exposee = db.Column(db.Boolean, default=False)
+
+@app.route("/", methods=["GET"])
+def index():
+ return "Public API", 200
+
+@app.route("/artistes", methods=["GET"])
+def get_artistes():
+ artistes = Artiste.query.all()
+ return jsonify([{"id": a.id, "nom": a.nom} for a in artistes]), 200
+
+@app.route("/galeries", methods=["GET"])
+def get_galeries():
+ galeries = Galerie.query.all()
+ return jsonify([{"id": g.id, "nom": g.nom} for g in galeries]), 200
+
+@app.route("/oeuvres", methods=["GET"])
+def get_oeuvres():
+ oeuvres = Oeuvre.query.filter_by(exposee=True).all()
+ return jsonify([{"id": o.id, "titre": o.titre} for o in oeuvres]), 200
+
+if __name__ == "__main__":
+ with app.app_context():
+ db.create_all()
+ app.run(port=5001)
+