Compare commits

...

15 Commits

8 changed files with 288 additions and 74 deletions

View File

@@ -46,7 +46,7 @@ public class SecurityConfig {
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() // autoriser les preflight .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() // autoriser les preflight
.requestMatchers("/api/auth/**").permitAll() .requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/users/**").authenticated() .requestMatchers("/api/users/**").authenticated()
.requestMatchers("/api/app/**").permitAll() .requestMatchers("/api/app/**").authenticated()
.anyRequest().permitAll() .anyRequest().permitAll()
) )
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
@@ -61,16 +61,26 @@ public class SecurityConfig {
@Bean @Bean
public CorsConfigurationSource corsConfigurationSource() { public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration(); CorsConfiguration config = new CorsConfiguration();
config.setAllowedOriginPatterns(Arrays.asList(
// IMPORTANT : origins explicites, sans path
config.setAllowedOrigins(Arrays.asList(
"http://localhost:4200", "http://localhost:4200",
"http://127.0.0.1:4200", "http://127.0.0.1:4200",
"https://dev.vincent-guillet.fr" "https://dev.vincent-guillet.fr",
"https://projets.vincent-guillet.fr"
)); ));
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
config.setAllowedHeaders(Arrays.asList("Authorization","Content-Type","Accept"));
config.setExposedHeaders(Arrays.asList("Authorization"));
config.setAllowCredentials(true); config.setAllowCredentials(true);
// Autoriser tous les headers côté requête (plus robuste)
config.setAllowedHeaders(Arrays.asList("*"));
// Autoriser les méthodes classiques
config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
// Headers que le client *voit* dans la réponse
config.setExposedHeaders(Arrays.asList("Authorization", "Content-Type"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config); source.registerCorsConfiguration("/**", config);
return source; return source;

View File

@@ -17,12 +17,6 @@ import java.util.Arrays;
@RestController @RestController
@RequestMapping("/api/auth") @RequestMapping("/api/auth")
@CrossOrigin(
origins = "https://dev.vincent-guillet.fr",
allowCredentials = "true",
allowedHeaders = "*",
methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.OPTIONS}
)
public class AuthController { public class AuthController {
private final AuthService authService; private final AuthService authService;

View File

@@ -5,7 +5,7 @@ import {LoginComponent} from './pages/auth/login/login.component';
import {ProfileComponent} from './pages/profile/profile.component'; import {ProfileComponent} from './pages/profile/profile.component';
import {guestOnlyCanActivate, guestOnlyCanMatch} from './guards/guest-only.guard'; import {guestOnlyCanActivate, guestOnlyCanMatch} from './guards/guest-only.guard';
import {adminOnlyCanActivate, adminOnlyCanMatch} from './guards/admin-only.guard'; import {adminOnlyCanActivate, adminOnlyCanMatch} from './guards/admin-only.guard';
import {authOnlyCanMatch} from './guards/auth-only.guard'; import {authOnlyCanActivate, authOnlyCanMatch} from './guards/auth-only.guard';
import {PsAdminComponent} from './pages/admin/ps-admin/ps-admin.component'; import {PsAdminComponent} from './pages/admin/ps-admin/ps-admin.component';
import {ProductsComponent} from './pages/products/products.component'; import {ProductsComponent} from './pages/products/products.component';
@@ -40,13 +40,13 @@ export const routes: Routes = [
path: 'profile', path: 'profile',
component: ProfileComponent, component: ProfileComponent,
canMatch: [authOnlyCanMatch], canMatch: [authOnlyCanMatch],
canActivate: [authOnlyCanMatch] canActivate: [authOnlyCanActivate]
}, },
{ {
path: 'products', path: 'products',
component: ProductsComponent, component: ProductsComponent,
canMatch: [authOnlyCanMatch], canMatch: [adminOnlyCanMatch],
canActivate: [authOnlyCanMatch] canActivate: [adminOnlyCanActivate]
}, },
{ {
path: 'admin', path: 'admin',

View File

@@ -84,6 +84,7 @@
.container { .container {
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
justify-content: center;
padding: 8px; padding: 8px;
} }

View File

@@ -1,5 +1,4 @@
version: "3.9" # docker-compose.dev.yml
services: services:
mysql: mysql:
image: mysql:8.4 image: mysql:8.4
@@ -9,11 +8,10 @@ services:
MYSQL_DATABASE: gameovergne_app MYSQL_DATABASE: gameovergne_app
MYSQL_USER: gameovergne MYSQL_USER: gameovergne
MYSQL_PASSWORD: gameovergne MYSQL_PASSWORD: gameovergne
# 🔒 Persistant + accessible depuis l'extérieur
volumes: volumes:
- ./mysql-data:/var/lib/mysql - ./mysql-data:/var/lib/mysql
ports: ports:
- "3366:3306" # pour te connecter depuis ton Mac / un client SQL - "3366:3306"
networks: networks:
- gameovergne - gameovergne
restart: unless-stopped restart: unless-stopped
@@ -35,16 +33,14 @@ services:
SPRING_DATASOURCE_PASSWORD: gameovergne SPRING_DATASOURCE_PASSWORD: gameovergne
PRESTASHOP_API_KEY: 2AQPG13MJ8X117U6FJ5NGHPS93HE34AB PRESTASHOP_API_KEY: 2AQPG13MJ8X117U6FJ5NGHPS93HE34AB
SERVER_PORT: 3000 SERVER_PORT: 3000
# pense bien à avoir prestashop.base-url / prestashop.basic-auth dans application.properties ou via env si besoin
networks: networks:
- gameovergne
- traefik - traefik
- gameovergne
restart: unless-stopped restart: unless-stopped
labels: labels:
- traefik.enable=true - traefik.enable=true
- traefik.docker.network=traefik - traefik.docker.network=traefik
# API sous /gameovergne-api
- traefik.http.routers.gameovergne-api.rule=Host(`dev.vincent-guillet.fr`) && PathPrefix(`/gameovergne-api`) - traefik.http.routers.gameovergne-api.rule=Host(`dev.vincent-guillet.fr`) && PathPrefix(`/gameovergne-api`)
- traefik.http.routers.gameovergne-api.entrypoints=edge - traefik.http.routers.gameovergne-api.entrypoints=edge
- traefik.http.routers.gameovergne-api.service=gameovergne-api - traefik.http.routers.gameovergne-api.service=gameovergne-api
@@ -65,24 +61,19 @@ services:
- traefik.enable=true - traefik.enable=true
- traefik.docker.network=traefik - traefik.docker.network=traefik
# FRONT sous /gameovergne (avec et sans slash final)
- traefik.http.routers.gameovergne-client.rule=Host(`dev.vincent-guillet.fr`) && (Path(`/gameovergne`) || PathPrefix(`/gameovergne/`)) - traefik.http.routers.gameovergne-client.rule=Host(`dev.vincent-guillet.fr`) && (Path(`/gameovergne`) || PathPrefix(`/gameovergne/`))
- traefik.http.routers.gameovergne-client.entrypoints=edge - traefik.http.routers.gameovergne-client.entrypoints=edge
- traefik.http.routers.gameovergne-client.service=gameovergne-client - traefik.http.routers.gameovergne-client.service=gameovergne-client
- traefik.http.routers.gameovergne-client.middlewares=gameovergne-slash,gameovergne-client-stripprefix - traefik.http.routers.gameovergne-client.middlewares=gameovergne-slash,gameovergne-client-stripprefix
# Redirige /gameovergne vers /gameovergne/
- traefik.http.middlewares.gameovergne-slash.redirectregex.regex=^https?://([^/]+)/gameovergne$$ - traefik.http.middlewares.gameovergne-slash.redirectregex.regex=^https?://([^/]+)/gameovergne$$
- traefik.http.middlewares.gameovergne-slash.redirectregex.replacement=https://$${1}/gameovergne/ - traefik.http.middlewares.gameovergne-slash.redirectregex.replacement=https://$${1}/gameovergne/
- traefik.http.middlewares.gameovergne-slash.redirectregex.permanent=true - traefik.http.middlewares.gameovergne-slash.redirectregex.permanent=true
# Enlève /gameovergne avant d'envoyer vers Nginx (le conteneur Angular)
- traefik.http.middlewares.gameovergne-client-stripprefix.stripprefix.prefixes=/gameovergne - traefik.http.middlewares.gameovergne-client-stripprefix.stripprefix.prefixes=/gameovergne
# Service vers Nginx (port 80 dans le conteneur)
- traefik.http.services.gameovergne-client.loadbalancer.server.port=80 - traefik.http.services.gameovergne-client.loadbalancer.server.port=80
# Proxy Presta via /gameovergne/ps -> même service, même StripPrefix
- traefik.http.routers.gameovergne-ps.rule=Host(`dev.vincent-guillet.fr`) && PathPrefix(`/gameovergne/ps`) - traefik.http.routers.gameovergne-ps.rule=Host(`dev.vincent-guillet.fr`) && PathPrefix(`/gameovergne/ps`)
- traefik.http.routers.gameovergne-ps.entrypoints=edge - traefik.http.routers.gameovergne-ps.entrypoints=edge
- traefik.http.routers.gameovergne-ps.service=gameovergne-client - traefik.http.routers.gameovergne-ps.service=gameovergne-client
@@ -92,4 +83,4 @@ networks:
traefik: traefik:
external: true external: true
gameovergne: gameovergne:
driver: bridge external: true

86
docker-compose.prod.yml Normal file
View File

@@ -0,0 +1,86 @@
# docker-compose.prod.yml
services:
mysql:
image: mysql:8.4
container_name: gameovergne-mysql-prod
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: gameovergne_app
MYSQL_USER: gameovergne
MYSQL_PASSWORD: gameovergne
volumes:
- ./mysql-data-prod:/var/lib/mysql
ports:
- "3366:3306"
networks:
- gameovergne
restart: unless-stopped
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-u", "root", "-proot"]
interval: 10s
timeout: 5s
retries: 10
spring:
image: registry.vincent-guillet.fr/gameovergne-api:prod-latest
container_name: gameovergne-api-prod
depends_on:
mysql:
condition: service_healthy
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/gameovergne_app?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
SPRING_DATASOURCE_USERNAME: gameovergne
SPRING_DATASOURCE_PASSWORD: gameovergne
PRESTASHOP_API_KEY: 2AQPG13MJ8X117U6FJ5NGHPS93HE34AB
SERVER_PORT: 3000
networks:
- traefik
- gameovergne
restart: unless-stopped
labels:
- traefik.enable=true
- traefik.docker.network=traefik
- traefik.http.routers.gameovergne-api.rule=Host(`projets.vincent-guillet.fr`) && PathPrefix(`/gameovergne-api`)
- traefik.http.routers.gameovergne-api.entrypoints=edge
- traefik.http.routers.gameovergne-api.service=gameovergne-api
- traefik.http.services.gameovergne-api.loadbalancer.server.port=3000
- traefik.http.routers.gameovergne-api.middlewares=gameovergne-api-stripprefix
- traefik.http.middlewares.gameovergne-api-stripprefix.stripprefix.prefixes=/gameovergne-api
angular:
image: registry.vincent-guillet.fr/gameovergne-client:prod-latest
container_name: gameovergne-client-prod
depends_on:
- spring
networks:
- gameovergne
- traefik
restart: unless-stopped
labels:
- traefik.enable=true
- traefik.docker.network=traefik
- traefik.http.routers.gameovergne-client.rule=Host(`projets.vincent-guillet.fr`) && (Path(`/gameovergne`) || PathPrefix(`/gameovergne/`))
- traefik.http.routers.gameovergne-client.entrypoints=edge
- traefik.http.routers.gameovergne-client.service=gameovergne-client
- traefik.http.routers.gameovergne-client.middlewares=gameovergne-slash,gameovergne-client-stripprefix
- traefik.http.middlewares.gameovergne-slash.redirectregex.regex=^https?://([^/]+)/gameovergne$$
- traefik.http.middlewares.gameovergne-slash.redirectregex.replacement=https://$${1}/gameovergne/
- traefik.http.middlewares.gameovergne-slash.redirectregex.permanent=true
- traefik.http.middlewares.gameovergne-client-stripprefix.stripprefix.prefixes=/gameovergne
- traefik.http.services.gameovergne-client.loadbalancer.server.port=80
- traefik.http.routers.gameovergne-ps.rule=Host(`projets.vincent-guillet.fr`) && PathPrefix(`/gameovergne/ps`)
- traefik.http.routers.gameovergne-ps.entrypoints=edge
- traefik.http.routers.gameovergne-ps.service=gameovergne-client
- traefik.http.routers.gameovergne-ps.middlewares=gameovergne-client-stripprefix
networks:
traefik:
external: true
gameovergne:
external: true

85
docker-compose.yml.OLD Normal file
View File

@@ -0,0 +1,85 @@
services:
mysql:
image: mysql:8.4
container_name: gameovergne-mysql
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: gameovergne_app
MYSQL_USER: gameovergne
MYSQL_PASSWORD: gameovergne
volumes:
- ./mysql-data:/var/lib/mysql
ports:
- "3366:3306"
networks:
- gameovergne
restart: unless-stopped
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-u", "root", "-proot"]
interval: 10s
timeout: 5s
retries: 10
spring:
image: registry.vincent-guillet.fr/gameovergne-api:dev-latest
container_name: gameovergne-api
depends_on:
mysql:
condition: service_healthy
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/gameovergne_app?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
SPRING_DATASOURCE_USERNAME: gameovergne
SPRING_DATASOURCE_PASSWORD: gameovergne
PRESTASHOP_API_KEY: 2AQPG13MJ8X117U6FJ5NGHPS93HE34AB
SERVER_PORT: 3000
networks:
- traefik
- gameovergne
restart: unless-stopped
labels:
- traefik.enable=true
- traefik.docker.network=traefik
- traefik.http.routers.gameovergne-api.rule=Host(`dev.vincent-guillet.fr`) && PathPrefix(`/gameovergne-api`)
- traefik.http.routers.gameovergne-api.entrypoints=edge
- traefik.http.routers.gameovergne-api.service=gameovergne-api
- traefik.http.services.gameovergne-api.loadbalancer.server.port=3000
- traefik.http.routers.gameovergne-api.middlewares=gameovergne-api-stripprefix
- traefik.http.middlewares.gameovergne-api-stripprefix.stripprefix.prefixes=/gameovergne-api
angular:
image: registry.vincent-guillet.fr/gameovergne-client:dev-latest
container_name: gameovergne-client
depends_on:
- spring
networks:
- gameovergne
- traefik
restart: unless-stopped
labels:
- traefik.enable=true
- traefik.docker.network=traefik
- traefik.http.routers.gameovergne-client.rule=Host(`dev.vincent-guillet.fr`) && (Path(`/gameovergne`) || PathPrefix(`/gameovergne/`))
- traefik.http.routers.gameovergne-client.entrypoints=edge
- traefik.http.routers.gameovergne-client.service=gameovergne-client
- traefik.http.routers.gameovergne-client.middlewares=gameovergne-slash,gameovergne-client-stripprefix
- traefik.http.middlewares.gameovergne-slash.redirectregex.regex=^https?://([^/]+)/gameovergne$$
- traefik.http.middlewares.gameovergne-slash.redirectregex.replacement=https://$${1}/gameovergne/
- traefik.http.middlewares.gameovergne-slash.redirectregex.permanent=true
- traefik.http.middlewares.gameovergne-client-stripprefix.stripprefix.prefixes=/gameovergne
- traefik.http.services.gameovergne-client.loadbalancer.server.port=80
- traefik.http.routers.gameovergne-ps.rule=Host(`dev.vincent-guillet.fr`) && PathPrefix(`/gameovergne/ps`)
- traefik.http.routers.gameovergne-ps.entrypoints=edge
- traefik.http.routers.gameovergne-ps.service=gameovergne-client
- traefik.http.routers.gameovergne-ps.middlewares=gameovergne-client-stripprefix
networks:
traefik:
external: true
gameovergne:
external: true

View File

@@ -1,72 +1,119 @@
pipeline { pipeline {
agent any agent none
tools {
maven 'mvn'
nodejs 'npm'
}
environment { environment {
JAVA_HOME = '/opt/java/openjdk' REGISTRY = 'registry.vincent-guillet.fr'
PATH = "${JAVA_HOME}/bin:${env.PATH}" API_IMAGE_DEV = "${REGISTRY}/gameovergne-api:dev-latest"
SPRING_IMAGE_NAME = 'spring-jenkins' CLIENT_IMAGE_DEV = "${REGISTRY}/gameovergne-client:dev-latest"
ANGULAR_IMAGE_NAME = 'angular-jenkins' API_IMAGE_PROD = "${REGISTRY}/gameovergne-api:prod-latest"
IMAGE_TAG = 'latest' CLIENT_IMAGE_PROD = "${REGISTRY}/gameovergne-client:prod-latest"
COMPOSE_PROJECT = 'gameovergne-app' COMPOSE_PROJECT = 'gameovergne-app'
} }
stages { stages {
stage('Checkout sur la branche dev') {
// Build & push images (toujours sur ct-home-dev)
stage('Build & Push Docker Images') {
agent { label 'ct-home-dev' }
steps { steps {
git branch: 'dev', url: 'https://gitea.vincent-guillet.fr/vincentguillet/gameovergne-app.git' // Multi-branch friendly : Jenkins fait le checkout de la branche courante
checkout scm
script {
// Choix des tags selon la branche
if (env.BRANCH_NAME == 'main') {
env.API_IMAGE = API_IMAGE_PROD
env.CLIENT_IMAGE = CLIENT_IMAGE_PROD
} else {
env.API_IMAGE = API_IMAGE_DEV
env.CLIENT_IMAGE = CLIENT_IMAGE_DEV
} }
} }
stage('Maven Build') { // ----- Build API -----
steps {
dir('api') { dir('api') {
sh 'mvn clean package -DskipTests' sh """
} echo "=== Build image API ${API_IMAGE} ==="
} docker build -t ${API_IMAGE} .
"""
} }
stage('Angular Build') { // ----- Build Client -----
steps {
dir('client') { dir('client') {
sh 'npm install' sh """
sh 'npm run build' echo "=== Build image CLIENT ${CLIENT_IMAGE} ==="
docker build -t ${CLIENT_IMAGE} .
"""
} }
// ----- Push vers registry -----
sh """
echo "=== Push images vers ${REGISTRY} ==="
docker push ${API_IMAGE}
docker push ${CLIENT_IMAGE}
"""
} }
} }
stage('Spring Docker Build') { // Déploiement DEV (ct-home-dev, branche dev)
steps { stage('Deploy DEV') {
sh 'docker build -t registry.vincent-guillet.fr/gameovergne-api:dev-latest ./api' when {
} branch 'dev'
} }
agent { label 'ct-home-dev' }
stage('Angular Docker Build') {
steps { steps {
sh 'docker build -t registry.vincent-guillet.fr/gameovergne-client:dev-latest ./client' checkout scm
}
}
stage('Deployment') {
steps {
withEnv([ withEnv([
"DOCKER_HOST=unix:///var/run/docker.sock", "DOCKER_HOST=unix:///var/run/docker.sock",
"COMPOSE_PROJECT_NAME=${env.COMPOSE_PROJECT}" "COMPOSE_PROJECT_NAME=${env.COMPOSE_PROJECT}"
]) { ]) {
sh ''' sh """
echo "=== Nettoyage des anciens conteneurs nommés ===" echo "=== [DEV] Nettoyage anciens conteneurs ==="
docker rm -f gameovergne-api gameovergne-client 2>/dev/null || true docker rm -f gameovergne-api gameovergne-client 2>/dev/null || true
echo "=== docker-compose down sur le projet courant ===" echo "=== [DEV] docker compose down ==="
docker-compose down -v || true docker compose -f docker-compose.dev.yml down -v || true
echo "=== (Re)création de la stack MySQL + Spring + Angular ===" echo "=== [DEV] docker compose pull ==="
docker-compose up -d mysql spring angular docker compose -f docker-compose.dev.yml pull
'''
echo "=== [DEV] docker compose up (force recreate) ==="
docker compose -f docker-compose.dev.yml up -d --force-recreate mysql spring angular
"""
}
}
}
// Déploiement PROD (ct-home-projets, branche main)
stage('Deploy PROD') {
when {
branch 'main'
}
agent { label 'ct-home-projets' }
steps {
checkout scm
withEnv([
"DOCKER_HOST=unix:///var/run/docker.sock",
"COMPOSE_PROJECT_NAME=${env.COMPOSE_PROJECT}"
]) {
sh """
echo "=== [PROD] Nettoyage anciens conteneurs ==="
docker rm -f gameovergne-api-prod gameovergne-client-prod 2>/dev/null || true
echo "=== [PROD] docker compose down ==="
docker compose -f docker-compose.prod.yml down || true
echo "=== [PROD] docker compose pull ==="
docker compose -f docker-compose.prod.yml pull
echo "=== [PROD] docker compose up (force recreate) ==="
docker compose -f docker-compose.prod.yml up -d --force-recreate mysql spring angular
"""
} }
} }
} }