Compare commits
18 Commits
00f45ae6c7
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| b79068623f | |||
|
|
3eed3d251f | ||
| 7dcc85ac95 | |||
| ec9eb0dc7d | |||
| 01cafd5904 | |||
| 321e2fd546 | |||
| 3026f0a13f | |||
| 52d17e5ad8 | |||
| 2803e910bd | |||
| 653ce83c33 | |||
| ce618deecf | |||
| 5331ce7866 | |||
|
|
6f6d033be3 | ||
|
|
ff8536b448 | ||
|
|
60593f6c11 | ||
|
|
1708c1bead | ||
|
|
dc33d762a1 | ||
|
|
e04cac3345 |
@@ -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(
|
|
||||||
"http://localhost:4200",
|
// IMPORTANT : origins explicites, sans path
|
||||||
"http://127.0.0.1:4200",
|
config.setAllowedOrigins(Arrays.asList(
|
||||||
"https://dev.vincent-guillet.fr"
|
"http://localhost:4200",
|
||||||
));
|
"http://127.0.0.1:4200",
|
||||||
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
|
"https://dev.vincent-guillet.fr",
|
||||||
config.setAllowedHeaders(Arrays.asList("Authorization","Content-Type","Accept"));
|
"https://projets.vincent-guillet.fr"
|
||||||
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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -1,32 +1,41 @@
|
|||||||
import {APP_INITIALIZER, ApplicationConfig, inject, provideZoneChangeDetection} from '@angular/core';
|
import {
|
||||||
|
APP_INITIALIZER,
|
||||||
|
ApplicationConfig,
|
||||||
|
inject,
|
||||||
|
provideZoneChangeDetection,
|
||||||
|
importProvidersFrom
|
||||||
|
} from '@angular/core';
|
||||||
import {provideRouter} from '@angular/router';
|
import {provideRouter} from '@angular/router';
|
||||||
|
import {BrowserModule} from '@angular/platform-browser';
|
||||||
|
import {APP_BASE_HREF} from '@angular/common';
|
||||||
import {routes} from './app.routes';
|
import {routes} from './app.routes';
|
||||||
import {provideHttpClient, withInterceptors} from '@angular/common/http';
|
import {provideHttpClient, withInterceptors} from '@angular/common/http';
|
||||||
import {provideAnimationsAsync} from '@angular/platform-browser/animations/async';
|
import {provideAnimationsAsync} from '@angular/platform-browser/animations/async';
|
||||||
import {authTokenInterceptor} from './interceptors/auth-token.interceptor';
|
import {authTokenInterceptor} from './interceptors/auth-token.interceptor';
|
||||||
import {AuthService} from './services/auth.service';
|
import {AuthService} from './services/auth.service';
|
||||||
import {catchError, firstValueFrom, of} from 'rxjs';
|
import {catchError, firstValueFrom, of} from 'rxjs';
|
||||||
|
import {environment} from '../environments/environment';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
provideZoneChangeDetection({eventCoalescing: true}),
|
provideZoneChangeDetection({eventCoalescing: true}),
|
||||||
|
importProvidersFrom(BrowserModule),
|
||||||
|
{provide: APP_BASE_HREF, useValue: environment.hrefBase},
|
||||||
provideRouter(routes),
|
provideRouter(routes),
|
||||||
provideAnimationsAsync(),
|
provideAnimationsAsync(),
|
||||||
provideHttpClient(withInterceptors([
|
provideHttpClient(withInterceptors([authTokenInterceptor])),
|
||||||
authTokenInterceptor
|
|
||||||
])
|
|
||||||
),
|
|
||||||
{
|
{
|
||||||
provide: APP_INITIALIZER,
|
provide: APP_INITIALIZER,
|
||||||
multi: true,
|
multi: true,
|
||||||
useFactory: () => {
|
useFactory: () => {
|
||||||
const auth = inject(AuthService);
|
const auth = inject(AuthService);
|
||||||
return () => firstValueFrom(auth.bootstrapSession().pipe(
|
return () =>
|
||||||
catchError(err => of(null))
|
firstValueFrom(
|
||||||
)
|
auth.bootstrapSession().pipe(
|
||||||
);
|
catchError(() => of(null))
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, provideAnimationsAsync()
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/* src/app/components/main-navbar/main-navbar.component.css */
|
||||||
|
/* Ajout prise en charge safe-area et meilleure gestion des overflow */
|
||||||
|
|
||||||
|
.mat-toolbar {
|
||||||
|
/* protège contre les zones sensibles (notch / status bar) */
|
||||||
|
padding-top: constant(safe-area-inset-top);
|
||||||
|
padding-top: env(safe-area-inset-top);
|
||||||
|
position: relative;
|
||||||
|
z-index: 1000;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* wrapper principal */
|
||||||
.container {
|
.container {
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
@@ -8,19 +21,22 @@
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
min-height: 56px; /* assure une hauteur minimale utile sur mobile */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* marque / titre */
|
||||||
.brand {
|
.brand {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
min-width: 0;
|
min-width: 0; /* autorise le shrink */
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* actions (boutons, menu utilisateur) */
|
||||||
.nav-actions {
|
.nav-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
@@ -28,16 +44,47 @@
|
|||||||
flex: 0 1 auto;
|
flex: 0 1 auto;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
|
min-width: 0; /* important pour permettre la réduction des enfants */
|
||||||
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* icône dans mat-menu */
|
||||||
.mat-menu-item mat-icon {
|
.mat-menu-item mat-icon {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Empêcher les boutons de dépasser et couper le texte avec ellipsis */
|
||||||
|
.nav-actions button {
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Angular Material place le texte dans .mat-button-wrapper — on le tronque proprement */
|
||||||
|
.nav-actions button .mat-button-wrapper {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: calc(100% - 56px); /* espace pour icônes + padding */
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ajustements spécifiques pour petits écrans */
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
|
.mat-toolbar {
|
||||||
|
padding-top: env(safe-area-inset-top);
|
||||||
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,6 +102,7 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
|
padding-bottom: 4px; /* espace pour le scroll horizontal */
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-actions button {
|
.nav-actions button {
|
||||||
@@ -62,6 +110,11 @@
|
|||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-actions button .mat-button-wrapper {
|
||||||
|
max-width: calc(100% - 40px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-actions mat-icon {
|
.nav-actions mat-icon {
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ export const environment = {
|
|||||||
production: true,
|
production: true,
|
||||||
apiUrl: '/gameovergne-api/api',
|
apiUrl: '/gameovergne-api/api',
|
||||||
psUrl: '/gameovergne-api/api/ps',
|
psUrl: '/gameovergne-api/api/ps',
|
||||||
indexBase: '/gameovergne/',
|
hrefBase: '/gameovergne/',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ export const environment = {
|
|||||||
production: false,
|
production: false,
|
||||||
apiUrl: 'http://localhost:3000/api',
|
apiUrl: 'http://localhost:3000/api',
|
||||||
psUrl: '/ps',
|
psUrl: '/ps',
|
||||||
indexBase: '/',
|
hrefBase: '/',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,11 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>Game Over'gne App</title>
|
<title>Game Over'gne App</title>
|
||||||
<script>
|
<base href="/gameovergne/">
|
||||||
import {environment} from "./environments/environment.prod";
|
|
||||||
document.write('<base href="' + environment.indexBase + '">');
|
|
||||||
</script>
|
|
||||||
<base href="/">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
||||||
|
|||||||
@@ -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
86
docker-compose.prod.yml
Normal 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
85
docker-compose.yml.OLD
Normal 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
|
||||||
129
jenkinsfile
129
jenkinsfile
@@ -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') {
|
|
||||||
steps {
|
|
||||||
git branch: 'dev', url: 'https://gitea.vincent-guillet.fr/vincentguillet/gameovergne-app.git'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stage('Maven Build') {
|
// Build & push images (toujours sur ct-home-dev)
|
||||||
|
stage('Build & Push Docker Images') {
|
||||||
|
agent { label 'ct-home-dev' }
|
||||||
|
|
||||||
steps {
|
steps {
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Build API -----
|
||||||
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
|
||||||
|
"""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user