Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b92c09cf55 | ||
| 18cb9d5d80 | |||
|
|
4ba134dd69 | ||
|
|
5e7a744151 | ||
|
|
044b64152c | ||
|
|
4de3ffa0e0 | ||
|
|
5bdf578de9 | ||
|
|
bc1b757a96 | ||
|
|
24b664e85b | ||
|
|
8565e68062 | ||
|
|
a8a95b16a9 |
@@ -2,10 +2,10 @@
|
|||||||
.gitea
|
.gitea
|
||||||
.env.local
|
.env.local
|
||||||
.env.test
|
.env.test
|
||||||
docker/
|
infra/dev/
|
||||||
deploy/docker/docker-compose.prod.yml
|
infra/prod/docker-compose.yml
|
||||||
deploy/docker/deploy.sh
|
infra/prod/deploy.sh
|
||||||
deploy/docker/.env.example
|
infra/prod/.env.example
|
||||||
frontend/node_modules
|
frontend/node_modules
|
||||||
frontend/.nuxt
|
frontend/.nuxt
|
||||||
frontend/.output
|
frontend/.output
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ jobs:
|
|||||||
token: ${{ secrets.REGISTRY_TOKEN }}
|
token: ${{ secrets.REGISTRY_TOKEN }}
|
||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
|
|
||||||
- name: Create next tag from VERSION
|
- name: Create next tag from config/version.yaml
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -28,18 +28,18 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
changed_version=false
|
changed_version=false
|
||||||
if git diff --name-only "${{ gitea.event.before }}" "${{ gitea.event.after }}" | grep -q '^VERSION$'; then
|
if git diff --name-only "${{ gitea.event.before }}" "${{ gitea.event.after }}" | grep -q '^config/version\.yaml$'; then
|
||||||
changed_version=true
|
changed_version=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
read_version() {
|
read_version() {
|
||||||
cat VERSION | tr -d '[:space:]'
|
awk -F': *' '/app\.version:/{print $2}' config/version.yaml | tr -d '[:space:]' | tr -d "'\""
|
||||||
}
|
}
|
||||||
|
|
||||||
if $changed_version; then
|
if $changed_version; then
|
||||||
version="$(read_version)"
|
version="$(read_version)"
|
||||||
if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
echo "Invalid version in VERSION: $version" >&2
|
echo "Invalid version in version.yaml: $version" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
@@ -52,10 +52,10 @@ jobs:
|
|||||||
version="${major}.${minor}.$((patch + 1))"
|
version="${major}.${minor}.$((patch + 1))"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "$version" > VERSION
|
printf "parameters:\\n app.version: '%s'\\n" "$version" > config/version.yaml
|
||||||
git config user.name "gitea-actions"
|
git config user.name "gitea-actions"
|
||||||
git config user.email "gitea-actions@local"
|
git config user.email "gitea-actions@local"
|
||||||
git add VERSION
|
git add config/version.yaml
|
||||||
git commit -m "chore : bump version to v$version" || true
|
git commit -m "chore : bump version to v$version" || true
|
||||||
git push origin develop || true
|
git push origin develop || true
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
- name: Build Docker image
|
- name: Build Docker image
|
||||||
run: |
|
run: |
|
||||||
docker build \
|
docker build \
|
||||||
-f deploy/docker/Dockerfile.prod \
|
-f infra/prod/Dockerfile \
|
||||||
-t gitea.malio.fr/malio-dev/inventory:${{ gitea.ref_name }} \
|
-t gitea.malio.fr/malio-dev/inventory:${{ gitea.ref_name }} \
|
||||||
-t gitea.malio.fr/malio-dev/inventory:latest \
|
-t gitea.malio.fr/malio-dev/inventory:latest \
|
||||||
.
|
.
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -20,7 +20,7 @@
|
|||||||
###< phpunit/phpunit ###
|
###< phpunit/phpunit ###
|
||||||
|
|
||||||
###> docker ###
|
###> docker ###
|
||||||
docker/.env.docker.local
|
infra/dev/.env.docker.local
|
||||||
###< docker ###
|
###< docker ###
|
||||||
###> migration archives ###
|
###> migration archives ###
|
||||||
/_archives/
|
/_archives/
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ make start
|
|||||||
make install
|
make install
|
||||||
```
|
```
|
||||||
|
|
||||||
> Si `make start` échoue sur le port de la BDD, modifier `POSTGRES_PORT` dans `docker/.env.docker.local`.
|
> Si `make start` échoue sur le port de la BDD, modifier `POSTGRES_PORT` dans `infra/dev/.env.docker.local`.
|
||||||
|
|
||||||
### Que fait `make install` ?
|
### Que fait `make install` ?
|
||||||
|
|
||||||
@@ -254,7 +254,7 @@ Configuration PhpStorm / VSCode :
|
|||||||
- **Port** : `8081`
|
- **Port** : `8081`
|
||||||
- **Path mapping** : racine du projet → `/var/www/html`
|
- **Path mapping** : racine du projet → `/var/www/html`
|
||||||
|
|
||||||
> Sous WSL, modifier `XDEBUG_CLIENT_HOST` dans `docker/.env.docker.local` avec votre IP locale.
|
> Sous WSL, modifier `XDEBUG_CLIENT_HOST` dans `infra/dev/.env.docker.local` avec votre IP locale.
|
||||||
|
|
||||||
## Git
|
## Git
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
api_platform:
|
api_platform:
|
||||||
title: Inventory API
|
title: Inventory API
|
||||||
description: API de gestion d'inventaire industriel — machines, pièces, composants, produits.
|
description: API de gestion d'inventaire industriel — machines, pièces, composants, produits.
|
||||||
version: 1.9.1
|
version: 1.9.6
|
||||||
defaults:
|
defaults:
|
||||||
stateless: false
|
stateless: false
|
||||||
cache_headers:
|
cache_headers:
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ security:
|
|||||||
- { path: ^/api/admin, roles: ROLE_ADMIN }
|
- { path: ^/api/admin, roles: ROLE_ADMIN }
|
||||||
- { path: ^/api/docs, roles: PUBLIC_ACCESS }
|
- { path: ^/api/docs, roles: PUBLIC_ACCESS }
|
||||||
- { path: ^/api/health$, roles: PUBLIC_ACCESS }
|
- { path: ^/api/health$, roles: PUBLIC_ACCESS }
|
||||||
|
- { path: ^/api/maintenance/check$, roles: PUBLIC_ACCESS }
|
||||||
- { path: ^/_mcp, roles: ROLE_USER }
|
- { path: ^/_mcp, roles: ROLE_USER }
|
||||||
- { path: ^/docs, roles: PUBLIC_ACCESS }
|
- { path: ^/docs, roles: PUBLIC_ACCESS }
|
||||||
- { path: ^/contexts, roles: PUBLIC_ACCESS }
|
- { path: ^/contexts, roles: PUBLIC_ACCESS }
|
||||||
|
|||||||
2
config/version.yaml
Normal file
2
config/version.yaml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
parameters:
|
||||||
|
app.version: '1.9.10'
|
||||||
@@ -2,7 +2,7 @@ services:
|
|||||||
web:
|
web:
|
||||||
container_name: php-${DOCKER_APP_NAME}-apache
|
container_name: php-${DOCKER_APP_NAME}-apache
|
||||||
build:
|
build:
|
||||||
context: ./docker/php
|
context: ./infra/dev
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
args:
|
args:
|
||||||
DOCKER_PHP_VERSION: ${DOCKER_PHP_VERSION}
|
DOCKER_PHP_VERSION: ${DOCKER_PHP_VERSION}
|
||||||
@@ -20,9 +20,9 @@ services:
|
|||||||
- ~/.cache:/var/www/.cache # Pour la cache de composer
|
- ~/.cache:/var/www/.cache # Pour la cache de composer
|
||||||
- ~/.config:/var/www/.config # Pour la config de yarn
|
- ~/.config:/var/www/.config # Pour la config de yarn
|
||||||
- ~/.composer:/var/www/.composer # Pour la config de composer
|
- ~/.composer:/var/www/.composer # Pour la config de composer
|
||||||
- ./docker/php/config/php.ini:/usr/local/etc/php/php.ini
|
- ./infra/dev/php.ini:/usr/local/etc/php/php.ini
|
||||||
- ./docker/php/config/vhost.conf:/etc/apache2/sites-available/000-default.conf
|
- ./infra/dev/vhost.conf:/etc/apache2/sites-available/000-default.conf
|
||||||
- ./docker/php/config/docker-php-ext-xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
|
- ./infra/dev/xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
|
||||||
- ./LOG:/var/www/html/LOG
|
- ./LOG:/var/www/html/LOG
|
||||||
- ./LOG/logs_apache:/var/log/apache2/
|
- ./LOG/logs_apache:/var/log/apache2/
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
|
|||||||
30
frontend/app/composables/useMaintenance.ts
Normal file
30
frontend/app/composables/useMaintenance.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { ref } from 'vue'
|
||||||
|
import { useApi } from './useApi'
|
||||||
|
|
||||||
|
const maintenanceEnabled = ref(false)
|
||||||
|
|
||||||
|
export function useMaintenance() {
|
||||||
|
const { apiCall } = useApi()
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const fetchStatus = async () => {
|
||||||
|
const res = await apiCall<{ enabled: boolean }>('/admin/maintenance')
|
||||||
|
if (res.success && res.data) {
|
||||||
|
maintenanceEnabled.value = res.data.enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggle = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await apiCall<{ enabled: boolean }>('/admin/maintenance', { method: 'PUT' })
|
||||||
|
if (res.success && res.data) {
|
||||||
|
maintenanceEnabled.value = res.data.enabled
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { maintenanceEnabled, loading, fetchStatus, toggle }
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useProfileSession, usePermissions } from "#imports";
|
import { useProfileSession, usePermissions, useApi } from "#imports";
|
||||||
|
|
||||||
export default defineNuxtRouteMiddleware(async (to) => {
|
export default defineNuxtRouteMiddleware(async (to) => {
|
||||||
const { ensureSession, activeProfile } = useProfileSession();
|
const { ensureSession, activeProfile } = useProfileSession();
|
||||||
@@ -12,9 +12,10 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
|||||||
normalizedPath.startsWith("/profiles") ||
|
normalizedPath.startsWith("/profiles") ||
|
||||||
fullPath.startsWith("/profiles") ||
|
fullPath.startsWith("/profiles") ||
|
||||||
routeName.startsWith("profiles");
|
routeName.startsWith("profiles");
|
||||||
|
const isMaintenanceRoute = normalizedPath === "/maintenance";
|
||||||
|
|
||||||
// Redirect to login if no active profile
|
// Redirect to login if no active profile
|
||||||
if (!activeProfile.value && !isProfilesRoute) {
|
if (!activeProfile.value && !isProfilesRoute && !isMaintenanceRoute) {
|
||||||
return navigateTo("/profiles");
|
return navigateTo("/profiles");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,5 +30,13 @@ export default defineNuxtRouteMiddleware(async (to) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Maintenance mode check for non-admin users
|
||||||
|
if (!isAdmin.value && !isMaintenanceRoute && !isProfilesRoute) {
|
||||||
|
const { apiCall } = useApi();
|
||||||
|
const res = await apiCall<{ enabled: boolean }>('/maintenance/check');
|
||||||
|
if (res.success && res.data?.enabled) {
|
||||||
|
return navigateTo("/maintenance");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container mx-auto p-6 max-w-6xl">
|
<div class="container mx-auto p-6 max-w-6xl">
|
||||||
|
<!-- Maintenance Mode -->
|
||||||
|
<div class="alert mb-6" :class="maintenanceEnabled ? 'alert-warning' : 'alert-info'">
|
||||||
|
<div class="flex items-center justify-between w-full">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="font-medium">Mode maintenance</span>
|
||||||
|
<span v-if="maintenanceEnabled" class="badge badge-warning badge-sm">Actif</span>
|
||||||
|
<span v-else class="badge badge-ghost badge-sm">Inactif</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="btn btn-sm"
|
||||||
|
:class="maintenanceEnabled ? 'btn-ghost' : 'btn-warning'"
|
||||||
|
:disabled="maintenanceLoading"
|
||||||
|
@click="handleToggleMaintenance"
|
||||||
|
>
|
||||||
|
<span v-if="maintenanceLoading" class="loading loading-spinner loading-xs" />
|
||||||
|
{{ maintenanceEnabled ? 'Désactiver' : 'Activer' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm opacity-70 mt-1">
|
||||||
|
{{ maintenanceEnabled ? 'Seuls les administrateurs peuvent accéder à l\'application.' : 'L\'application est accessible à tous les utilisateurs.' }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-between mb-6">
|
<div class="flex items-center justify-between mb-6">
|
||||||
<h1 class="text-2xl font-bold">
|
<h1 class="text-2xl font-bold">
|
||||||
Administration des profils
|
Administration des profils
|
||||||
@@ -153,9 +176,14 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import DataTable from '~/components/common/DataTable.vue'
|
import DataTable from '~/components/common/DataTable.vue'
|
||||||
import { useAdminProfiles } from '#imports'
|
import { useAdminProfiles, useMaintenance } from '#imports'
|
||||||
|
|
||||||
const { profiles, loading, fetchAll, createProfile, updateRole, setPassword, deactivateProfile } = useAdminProfiles()
|
const { profiles, loading, fetchAll, createProfile, updateRole, setPassword, deactivateProfile } = useAdminProfiles()
|
||||||
|
const { maintenanceEnabled, loading: maintenanceLoading, fetchStatus: fetchMaintenanceStatus, toggle: toggleMaintenance } = useMaintenance()
|
||||||
|
|
||||||
|
const handleToggleMaintenance = async () => {
|
||||||
|
await toggleMaintenance()
|
||||||
|
}
|
||||||
|
|
||||||
const loaded = ref(false)
|
const loaded = ref(false)
|
||||||
const isLoading = computed(() => loading.value || !loaded.value)
|
const isLoading = computed(() => loading.value || !loaded.value)
|
||||||
@@ -264,7 +292,7 @@ const handleDeactivate = async (profileId) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await fetchAll()
|
await Promise.all([fetchAll(), fetchMaintenanceStatus()])
|
||||||
loaded.value = true
|
loaded.value = true
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
21
frontend/app/pages/maintenance.vue
Normal file
21
frontend/app/pages/maintenance.vue
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<template>
|
||||||
|
<div class="min-h-screen flex items-center justify-center bg-base-200">
|
||||||
|
<div class="text-center max-w-md">
|
||||||
|
<h1 class="text-4xl font-bold mb-4">
|
||||||
|
Maintenance
|
||||||
|
</h1>
|
||||||
|
<p class="text-lg text-base-content/70 mb-6">
|
||||||
|
L'application est actuellement en maintenance. Veuillez réessayer ultérieurement.
|
||||||
|
</p>
|
||||||
|
<button class="btn btn-primary" @click="retry">
|
||||||
|
Réessayer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const retry = () => {
|
||||||
|
navigateTo('/')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -147,7 +147,7 @@ defineEmits<{
|
|||||||
|
|
||||||
**Step 2: Run lint**
|
**Step 2: Run lint**
|
||||||
|
|
||||||
Run: `cd frontend && npm run lint:fix`
|
Run: `cd Inventory_frontend && npm run lint:fix`
|
||||||
|
|
||||||
**Step 3: Replace document list in each of the 5 files**
|
**Step 3: Replace document list in each of the 5 files**
|
||||||
|
|
||||||
@@ -166,7 +166,7 @@ Remove the now-unused imports (`documentIcon`, `formatSize`, `shouldInlinePdf`,
|
|||||||
|
|
||||||
**Step 4: Run lint + typecheck**
|
**Step 4: Run lint + typecheck**
|
||||||
|
|
||||||
Run: `cd frontend && npm run lint:fix && npx nuxi typecheck`
|
Run: `cd Inventory_frontend && npm run lint:fix && npx nuxi typecheck`
|
||||||
|
|
||||||
**Step 5: Commit**
|
**Step 5: Commit**
|
||||||
|
|
||||||
@@ -258,7 +258,7 @@ Import sanitize/hydrate functions from the new files. File should end up ~270 li
|
|||||||
|
|
||||||
**Step 4: Verify all imports across the codebase still work**
|
**Step 4: Verify all imports across the codebase still work**
|
||||||
|
|
||||||
Run: `cd frontend && npx nuxi typecheck`
|
Run: `cd Inventory_frontend && npx nuxi typecheck`
|
||||||
|
|
||||||
**Step 5: Commit**
|
**Step 5: Commit**
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import { readFileSync } from 'node:fs'
|
|||||||
import { dirname, resolve } from 'node:path'
|
import { dirname, resolve } from 'node:path'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
// Lire la version depuis le fichier VERSION à la racine du projet parent
|
// Lire la version depuis config/version.yaml à la racine du projet parent
|
||||||
const getAppVersion = (): string => {
|
const getAppVersion = (): string => {
|
||||||
try {
|
try {
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||||
const versionPath = resolve(__dirname, '..', 'VERSION')
|
const versionPath = resolve(__dirname, '..', 'config', 'version.yaml')
|
||||||
return readFileSync(versionPath, 'utf-8').trim()
|
const content = readFileSync(versionPath, 'utf-8')
|
||||||
|
const match = content.match(/app\.version:\s*'([^']+)'/)
|
||||||
|
return match ? match[1] : '0.0.0'
|
||||||
} catch {
|
} catch {
|
||||||
return '0.0.0'
|
return '0.0.0'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ COPY migrations migrations/
|
|||||||
COPY public public/
|
COPY public public/
|
||||||
COPY src src/
|
COPY src src/
|
||||||
COPY templates templates/
|
COPY templates templates/
|
||||||
COPY VERSION VERSION
|
|
||||||
|
|
||||||
RUN composer dump-autoload --optimize --no-dev
|
RUN composer dump-autoload --optimize --no-dev
|
||||||
|
|
||||||
@@ -31,6 +30,7 @@ COPY frontend/package.json frontend/package-lock.json ./
|
|||||||
RUN npm ci
|
RUN npm ci
|
||||||
|
|
||||||
COPY frontend/ ./
|
COPY frontend/ ./
|
||||||
|
COPY config/version.yaml /app/config/version.yaml
|
||||||
ENV CI=1 \
|
ENV CI=1 \
|
||||||
NUXT_TELEMETRY_DISABLED=1 \
|
NUXT_TELEMETRY_DISABLED=1 \
|
||||||
NUXT_PUBLIC_API_BASE_URL=/api \
|
NUXT_PUBLIC_API_BASE_URL=/api \
|
||||||
@@ -61,8 +61,8 @@ RUN ln -sf /dev/stdout /var/log/nginx/access.log \
|
|||||||
RUN rm -f /etc/nginx/sites-enabled/default
|
RUN rm -f /etc/nginx/sites-enabled/default
|
||||||
|
|
||||||
# Configs
|
# Configs
|
||||||
COPY deploy/docker/supervisord.conf /etc/supervisor/conf.d/app.conf
|
COPY infra/prod/supervisord.conf /etc/supervisor/conf.d/app.conf
|
||||||
COPY deploy/docker/nginx.conf /etc/nginx/sites-enabled/inventory.conf
|
COPY infra/prod/nginx.conf /etc/nginx/sites-enabled/inventory.conf
|
||||||
|
|
||||||
# Backend from stage 1
|
# Backend from stage 1
|
||||||
COPY --from=backend-build /app /var/www/html
|
COPY --from=backend-build /app /var/www/html
|
||||||
@@ -24,5 +24,5 @@ echo "==> Clearing cache..."
|
|||||||
sudo docker compose exec -T -u www-data app php bin/console cache:clear --env=prod
|
sudo docker compose exec -T -u www-data app php bin/console cache:clear --env=prod
|
||||||
sudo docker compose exec -T -u www-data app php bin/console cache:warmup --env=prod
|
sudo docker compose exec -T -u www-data app php bin/console cache:warmup --env=prod
|
||||||
|
|
||||||
VERSION=$(sudo docker compose exec -T app cat VERSION)
|
VERSION=$(sudo docker compose exec -T app cat config/version.yaml | grep 'app.version' | awk -F"'" '{print $2}')
|
||||||
echo "==> Deployed v${VERSION}"
|
echo "==> Deployed v${VERSION}"
|
||||||
@@ -6,7 +6,7 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "8082:80"
|
- "8082:80"
|
||||||
volumes:
|
volumes:
|
||||||
- ./uploads:/var/www/html/var/uploads
|
- ./storage:/var/www/html/var/storage/documents
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- "host.docker.internal:host-gateway"
|
- "host.docker.internal:host-gateway"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
8
makefile
8
makefile
@@ -1,6 +1,6 @@
|
|||||||
# Permet d'utiliser un .env.docker.local pour override
|
# Permet d'utiliser un .env.docker.local pour override
|
||||||
ENV_DEFAULT = docker/.env.docker
|
ENV_DEFAULT = infra/dev/.env.docker
|
||||||
ENV_LOCAL = docker/.env.docker.local
|
ENV_LOCAL = infra/dev/.env.docker.local
|
||||||
ENV_FILE := $(if $(wildcard $(ENV_LOCAL)),$(ENV_LOCAL),$(ENV_DEFAULT))
|
ENV_FILE := $(if $(wildcard $(ENV_LOCAL)),$(ENV_LOCAL),$(ENV_DEFAULT))
|
||||||
|
|
||||||
# Permet d'avoir les variables du fichier .env.docker.local
|
# Permet d'avoir les variables du fichier .env.docker.local
|
||||||
@@ -25,13 +25,13 @@ DATA_SQL_NORM ?= data_norm.sql
|
|||||||
#========================================================================================
|
#========================================================================================
|
||||||
|
|
||||||
env-init:
|
env-init:
|
||||||
@mkdir -p docker
|
@mkdir -p infra/dev
|
||||||
@cp --update=none $(ENV_DEFAULT) $(ENV_LOCAL)
|
@cp --update=none $(ENV_DEFAULT) $(ENV_LOCAL)
|
||||||
|
|
||||||
# Lance le container
|
# Lance le container
|
||||||
start: env-init
|
start: env-init
|
||||||
@echo "**** START CONTAINERS ****"
|
@echo "**** START CONTAINERS ****"
|
||||||
@cp --update=none docker/.env.docker docker/.env.docker.local
|
@cp --update=none infra/dev/.env.docker infra/dev/.env.docker.local
|
||||||
CURRENT_UID=$(shell id -u) CURRENT_GID=$(shell id -g) $(DOCKER_COMPOSE) up -d
|
CURRENT_UID=$(shell id -u) CURRENT_GID=$(shell id -g) $(DOCKER_COMPOSE) up -d
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "URLs disponibles:"
|
@echo "URLs disponibles:"
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ NC='\033[0m' # No Color
|
|||||||
# Répertoire racine du projet
|
# Répertoire racine du projet
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||||
VERSION_FILE="$PROJECT_ROOT/VERSION"
|
VERSION_FILE="$PROJECT_ROOT/config/version.yaml"
|
||||||
API_PLATFORM_FILE="$PROJECT_ROOT/config/packages/api_platform.yaml"
|
API_PLATFORM_FILE="$PROJECT_ROOT/config/packages/api_platform.yaml"
|
||||||
|
|
||||||
# Lire la version actuelle
|
# Lire la version actuelle
|
||||||
current_version=$(cat "$VERSION_FILE" | tr -d '\n')
|
current_version=$(awk -F': *' '/app\.version:/{print $2}' "$VERSION_FILE" | tr -d "' \n\r")
|
||||||
|
|
||||||
# Fonction pour afficher l'aide
|
# Fonction pour afficher l'aide
|
||||||
show_help() {
|
show_help() {
|
||||||
@@ -113,8 +113,8 @@ cd "$PROJECT_ROOT"
|
|||||||
# ===========================================
|
# ===========================================
|
||||||
# ÉTAPE 1 : Mettre à jour VERSION
|
# ÉTAPE 1 : Mettre à jour VERSION
|
||||||
# ===========================================
|
# ===========================================
|
||||||
echo -e "${BLUE}[1/4]${NC} Mise à jour du fichier VERSION..."
|
echo -e "${BLUE}[1/4]${NC} Mise à jour de config/version.yaml..."
|
||||||
echo "$new_version" > "$VERSION_FILE"
|
printf "parameters:\\n app.version: '%s'\\n" "$new_version" > "$VERSION_FILE"
|
||||||
|
|
||||||
# ===========================================
|
# ===========================================
|
||||||
# ÉTAPE 2 : Mettre à jour api_platform.yaml
|
# ÉTAPE 2 : Mettre à jour api_platform.yaml
|
||||||
|
|||||||
58
src/Controller/MaintenanceController.php
Normal file
58
src/Controller/MaintenanceController.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpKernel\KernelInterface;
|
||||||
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
|
||||||
|
final class MaintenanceController extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly KernelInterface $kernel,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
#[Route('/api/maintenance/check', name: 'maintenance_check', methods: ['GET'])]
|
||||||
|
public function check(): JsonResponse
|
||||||
|
{
|
||||||
|
return new JsonResponse([
|
||||||
|
'enabled' => file_exists($this->flagPath()),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/admin/maintenance', name: 'admin_maintenance_status', methods: ['GET'])]
|
||||||
|
public function status(): JsonResponse
|
||||||
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
||||||
|
|
||||||
|
return new JsonResponse([
|
||||||
|
'enabled' => file_exists($this->flagPath()),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route('/api/admin/maintenance', name: 'admin_maintenance_toggle', methods: ['PUT'])]
|
||||||
|
public function toggle(): JsonResponse
|
||||||
|
{
|
||||||
|
$this->denyAccessUnlessGranted('ROLE_ADMIN');
|
||||||
|
|
||||||
|
$path = $this->flagPath();
|
||||||
|
|
||||||
|
if (file_exists($path)) {
|
||||||
|
unlink($path);
|
||||||
|
$enabled = false;
|
||||||
|
} else {
|
||||||
|
file_put_contents($path, (string) time());
|
||||||
|
$enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JsonResponse(['enabled' => $enabled]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function flagPath(): string
|
||||||
|
{
|
||||||
|
return $this->kernel->getProjectDir() . '/var/maintenance';
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/EventListener/MaintenanceModeListener.php
Normal file
60
src/EventListener/MaintenanceModeListener.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\EventListener;
|
||||||
|
|
||||||
|
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpKernel\Event\RequestEvent;
|
||||||
|
use Symfony\Component\HttpKernel\KernelInterface;
|
||||||
|
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||||
|
|
||||||
|
#[AsEventListener(event: 'kernel.request', priority: 10)]
|
||||||
|
final class MaintenanceModeListener
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly KernelInterface $kernel,
|
||||||
|
private readonly TokenStorageInterface $tokenStorage,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function __invoke(RequestEvent $event): void
|
||||||
|
{
|
||||||
|
if (!$event->isMainRequest()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$flagFile = $this->kernel->getProjectDir() . '/var/maintenance';
|
||||||
|
|
||||||
|
if (!file_exists($flagFile)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$request = $event->getRequest();
|
||||||
|
$path = $request->getPathInfo();
|
||||||
|
|
||||||
|
// Always allow maintenance status endpoint and session endpoints
|
||||||
|
if (str_starts_with($path, '/api/admin/maintenance')
|
||||||
|
|| str_starts_with($path, '/api/maintenance/check')
|
||||||
|
|| str_starts_with($path, '/api/session')
|
||||||
|
|| str_starts_with($path, '/api/health')
|
||||||
|
|| str_starts_with($path, '/api/docs')
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow admin users through
|
||||||
|
$token = $this->tokenStorage->getToken();
|
||||||
|
if ($token && $token->getUser()) {
|
||||||
|
$roles = $token->getRoleNames();
|
||||||
|
if (in_array('ROLE_ADMIN', $roles, true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$event->setResponse(new JsonResponse(
|
||||||
|
['message' => 'Application en maintenance. Veuillez réessayer ultérieurement.'],
|
||||||
|
JsonResponse::HTTP_SERVICE_UNAVAILABLE,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user