Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 29e6fec64d | |||
| df8af066fc | |||
| 7d52c1e440 | |||
| 69da55b292 | |||
| d0eb1252d5 | |||
| 9a0015f205 | |||
| 94b4218ba5 | |||
| f1825434ed | |||
| d11a297fd2 | |||
| 6680003b4d | |||
| 9ec95111a0 | |||
| 4ffce393de | |||
| 89b4107266 | |||
| 70d4767890 | |||
| c51f17a74c | |||
| ebe1836f7e | |||
| d75afe0aac | |||
| c3e1c95f1b | |||
| 6515d8348d | |||
| d0045a7d61 | |||
| 0074c28dcd | |||
| 4cba9ce74b | |||
| 89610b807c | |||
| 4633805dbd | |||
| 7f95b8a2ef | |||
| 8eb5d93514 | |||
| 15fcbcc9eb | |||
| 85ad36880c |
@@ -0,0 +1,21 @@
|
|||||||
|
<!-- Merci de remplir ce modèle pour faciliter la revue de code -->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
<!-- Que fait cette Pull Request ? -->
|
||||||
|
|
||||||
|
## Ticket lié
|
||||||
|
<!-- Ex : Closes #12 -->
|
||||||
|
|
||||||
|
## Type de changement
|
||||||
|
- [ ] 🐞 Correction de bug
|
||||||
|
- [ ] ✨ Nouvelle fonctionnalité
|
||||||
|
- [ ] ♻️ Refactoring / dette technique
|
||||||
|
- [ ] 🔒 Sécurité
|
||||||
|
- [ ] 📄 Documentation
|
||||||
|
|
||||||
|
## Checklist avant fusion
|
||||||
|
- [ ] Le code respecte le style du projet (`./vendor/bin/pint`)
|
||||||
|
- [ ] Les tests automatisés passent (`php artisan test`)
|
||||||
|
- [ ] Aucune dépendance vulnérable introduite (`composer audit`)
|
||||||
|
- [ ] La documentation a été mise à jour si nécessaire
|
||||||
|
- [ ] Aucun secret / `.env` n'est committé
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
name: "🐞 Rapport d'incident"
|
||||||
|
about: "Signaler un dysfonctionnement (bug) sur CESIZen"
|
||||||
|
title: "[BUG] "
|
||||||
|
labels:
|
||||||
|
- bug
|
||||||
|
body:
|
||||||
|
- type: dropdown
|
||||||
|
id: composant
|
||||||
|
attributes:
|
||||||
|
label: Composant concerné
|
||||||
|
options:
|
||||||
|
- Web (Laravel)
|
||||||
|
- Mobile (Flutter)
|
||||||
|
- Wear OS
|
||||||
|
- API
|
||||||
|
- Autre
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: gravite
|
||||||
|
attributes:
|
||||||
|
label: Gravité
|
||||||
|
options:
|
||||||
|
- Critique (service indisponible)
|
||||||
|
- Majeure (fonctionnalité bloquée)
|
||||||
|
- Mineure (contournement possible)
|
||||||
|
- Cosmétique
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Description du problème
|
||||||
|
description: Que se passe-t-il ?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: reproduction
|
||||||
|
attributes:
|
||||||
|
label: Étapes de reproduction
|
||||||
|
placeholder: |
|
||||||
|
1. Aller sur ...
|
||||||
|
2. Cliquer sur ...
|
||||||
|
3. Constater ...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: attendu
|
||||||
|
attributes:
|
||||||
|
label: Comportement attendu
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: environnement
|
||||||
|
attributes:
|
||||||
|
label: Environnement
|
||||||
|
placeholder: "Prod / Préprod — navigateur / OS / version"
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
name: "✨ Demande d'évolution"
|
||||||
|
about: "Proposer une nouvelle fonctionnalité ou une amélioration"
|
||||||
|
title: "[EVOL] "
|
||||||
|
labels:
|
||||||
|
- enhancement
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: besoin
|
||||||
|
attributes:
|
||||||
|
label: Besoin / Problème à résoudre
|
||||||
|
description: Quel besoin utilisateur ou métier cette évolution couvre-t-elle ?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: solution
|
||||||
|
attributes:
|
||||||
|
label: Solution proposée
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: priorite
|
||||||
|
attributes:
|
||||||
|
label: Priorité souhaitée
|
||||||
|
options:
|
||||||
|
- Haute
|
||||||
|
- Moyenne
|
||||||
|
- Basse
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: criteres
|
||||||
|
attributes:
|
||||||
|
label: Critères d'acceptation
|
||||||
|
placeholder: |
|
||||||
|
- [ ] ...
|
||||||
|
- [ ] ...
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
name: CI - Intégration continue
|
||||||
|
|
||||||
|
# Intégration continue : à chaque push et chaque pull request, on vérifie
|
||||||
|
# automatiquement la qualité du code (Laravel Pint, analyse statique Larastan)
|
||||||
|
# puis on exécute les tests automatisés (PHPUnit) avec couverture. Un
|
||||||
|
# déploiement (voir deploy.yml) ne doit se faire que si cette étape est au vert.
|
||||||
|
#
|
||||||
|
# NB : l'application Laravel vit dans le sous-dossier WEB/ (monorepo).
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- 'WEB/**'
|
||||||
|
- '.gitea/workflows/ci.yml'
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- 'WEB/**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
quality-and-tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
# Toutes les commandes s'exécutent dans le dossier de l'app Laravel.
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: WEB
|
||||||
|
|
||||||
|
# Jetons SonarQube, définis dans les secrets du dépôt Gitea.
|
||||||
|
# Exposés au niveau du job pour pouvoir conditionner l'étape d'analyse
|
||||||
|
# (elle ne s'exécute que si un jeton est configuré).
|
||||||
|
env:
|
||||||
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Récupération du code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
# Historique complet : SonarQube en a besoin pour dater le code
|
||||||
|
# et calculer correctement le "nouveau code".
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Installation de PHP 8.2
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: '8.2'
|
||||||
|
extensions: pdo_mysql, pdo_sqlite, sqlite3, mbstring, exif, pcntl, bcmath, gd, intl, zip
|
||||||
|
# pcov : moteur de couverture rapide, requis pour le rapport SonarQube.
|
||||||
|
coverage: pcov
|
||||||
|
|
||||||
|
- name: Cache des dépendances Composer
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: WEB/vendor
|
||||||
|
key: composer-${{ hashFiles('WEB/composer.lock') }}
|
||||||
|
|
||||||
|
- name: Installation des dépendances
|
||||||
|
run: composer install --no-interaction --prefer-dist --optimize-autoloader
|
||||||
|
|
||||||
|
- name: Préparation de l'environnement de test
|
||||||
|
run: |
|
||||||
|
cp .env.example .env
|
||||||
|
php artisan key:generate
|
||||||
|
|
||||||
|
- name: Vérification du style de code (Laravel Pint)
|
||||||
|
run: vendor/bin/pint --test
|
||||||
|
|
||||||
|
- name: Analyse statique (Larastan / PHPStan)
|
||||||
|
run: vendor/bin/phpstan analyse --configuration=phpstan.neon --memory-limit=512M --no-progress
|
||||||
|
|
||||||
|
- name: Audit de sécurité des dépendances (composer audit)
|
||||||
|
# Signale les vulnérabilités connues (CVE) des dépendances.
|
||||||
|
# Informatif : n'échoue pas le build, car un correctif amont peut ne
|
||||||
|
# pas être encore disponible ; les dépendances sont tenues à jour.
|
||||||
|
run: composer audit --no-interaction
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Exécution des tests + couverture (PHPUnit)
|
||||||
|
# phpunit.xml définit déjà l'environnement de test (sqlite en mémoire).
|
||||||
|
# On génère le rapport de couverture (clover) et le rapport de tests
|
||||||
|
# (junit) consommés ensuite par SonarQube.
|
||||||
|
run: |
|
||||||
|
mkdir -p coverage
|
||||||
|
vendor/bin/phpunit \
|
||||||
|
--coverage-clover=coverage/clover.xml \
|
||||||
|
--log-junit=coverage/junit.xml
|
||||||
|
|
||||||
|
- name: Analyse SonarQube
|
||||||
|
# Ne s'exécute que si le secret SONAR_TOKEN est configuré dans Gitea,
|
||||||
|
# sinon l'étape est ignorée (la CI reste verte tant que le serveur
|
||||||
|
# SonarQube n'est pas branché).
|
||||||
|
if: ${{ env.SONAR_TOKEN != '' }}
|
||||||
|
uses: sonarsource/sonarqube-scan-action@v5
|
||||||
|
with:
|
||||||
|
# L'app (et sonar-project.properties) se trouve dans WEB/.
|
||||||
|
projectBaseDir: WEB
|
||||||
|
# Le scanner lit sonar-project.properties et utilise SONAR_HOST_URL /
|
||||||
|
# SONAR_TOKEN exposés au niveau du job.
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
name: CD - Déploiement continu
|
||||||
|
|
||||||
|
# Déploiement continu : après un push sur main, une fois la CI passée,
|
||||||
|
# on se connecte en SSH au serveur de production et on met à jour l'application
|
||||||
|
# (git reset, rebuild des conteneurs, migrations, mise en cache).
|
||||||
|
#
|
||||||
|
# L'application Laravel vit dans le sous-dossier WEB/ (monorepo) ; le conteneur
|
||||||
|
# PHP de production s'appelle « cesizen-app » (cf. WEB/docker-compose.yml).
|
||||||
|
#
|
||||||
|
# Secrets à définir dans Gitea (Settings > Actions > Secrets du dépôt) :
|
||||||
|
# - SSH_HOST : IP ou domaine du serveur
|
||||||
|
# - SSH_USER : utilisateur SSH
|
||||||
|
# - SSH_PRIVATE_KEY : clé privée SSH autorisée sur le serveur
|
||||||
|
# - SSH_PORT : port SSH (optionnel, 22 par défaut)
|
||||||
|
# - DEPLOY_PATH : chemin du dépôt cloné sur le serveur (ex: /home/sam/CESIZen)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Déploiement sur le serveur via SSH
|
||||||
|
uses: appleboy/ssh-action@v1.2.0
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.SSH_HOST }}
|
||||||
|
username: ${{ secrets.SSH_USER }}
|
||||||
|
key: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||||
|
port: ${{ secrets.SSH_PORT }}
|
||||||
|
script: |
|
||||||
|
set -e
|
||||||
|
cd "${{ secrets.DEPLOY_PATH }}"
|
||||||
|
|
||||||
|
DEPLOY_UID=$(id -u)
|
||||||
|
|
||||||
|
# L'utilisateur de déploiement doit posséder storage/ & bootstrap/cache
|
||||||
|
# AVANT toute opération git (un déploiement précédent a pu les passer à
|
||||||
|
# www-data, ce qui bloquerait git). chown via le conteneur (= root).
|
||||||
|
docker exec -u 0 cesizen-app chown -R "${DEPLOY_UID}:www-data" \
|
||||||
|
/var/www/storage /var/www/bootstrap/cache || true
|
||||||
|
|
||||||
|
# Récupération propre de la dernière version : on ÉCRASE toute modif
|
||||||
|
# locale parasite (le serveur est une copie de déploiement, aucune
|
||||||
|
# édition manuelle n'est censée y vivre). Les fichiers non suivis
|
||||||
|
# (.env, cert.pem…) ne sont PAS touchés par reset --hard.
|
||||||
|
git fetch origin main
|
||||||
|
git reset --hard origin/main
|
||||||
|
|
||||||
|
# Reconstruction et redémarrage des conteneurs (compose dans WEB/)
|
||||||
|
cd WEB
|
||||||
|
docker compose up -d --build
|
||||||
|
|
||||||
|
# Le bind-mount ./:/var/www masque le vendor/ et public/build/
|
||||||
|
# construits dans l'image : on les (re)génère sur l'hôte, dans le
|
||||||
|
# conteneur, pour qu'ils soient présents via le montage.
|
||||||
|
docker exec cesizen-app composer install --no-interaction --no-dev --optimize-autoloader
|
||||||
|
docker exec cesizen-app npm ci
|
||||||
|
docker exec cesizen-app npm run build
|
||||||
|
|
||||||
|
# Migrations de la base de données (sans interaction)
|
||||||
|
docker exec cesizen-app php artisan migrate --force
|
||||||
|
|
||||||
|
# Mise en cache de la configuration et des routes (performances en prod)
|
||||||
|
docker exec cesizen-app php artisan config:cache
|
||||||
|
docker exec cesizen-app php artisan route:cache
|
||||||
|
docker exec cesizen-app php artisan view:cache
|
||||||
|
|
||||||
|
# Correction des permissions sur les dossiers écrits par Laravel.
|
||||||
|
# Propriétaire = utilisateur de déploiement (pour les futurs git reset),
|
||||||
|
# groupe = www-data avec écriture de groupe + setgid (héritage du groupe).
|
||||||
|
docker exec -u 0 cesizen-app sh -c "\
|
||||||
|
chown -R ${DEPLOY_UID}:www-data /var/www/storage /var/www/bootstrap/cache && \
|
||||||
|
chmod -R ug+rwX /var/www/storage /var/www/bootstrap/cache && \
|
||||||
|
find /var/www/storage /var/www/bootstrap/cache -type d -exec chmod g+s {} +"
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
# Dépendances
|
||||||
|
/node_modules
|
||||||
|
**/node_modules
|
||||||
|
|
||||||
|
# Environnement / secrets
|
||||||
|
.env
|
||||||
|
*.env.local
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Système
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs & temporaires
|
||||||
|
*.log
|
||||||
|
~$*
|
||||||
|
|
||||||
|
# Runner CI (données locales)
|
||||||
|
ci/act_runner/data/
|
||||||
Generated
-3
@@ -1,3 +0,0 @@
|
|||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
Generated
-52
@@ -1,52 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="JAVA_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
|
||||||
<exclude-output />
|
|
||||||
<content url="file://$MODULE_DIR$">
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/.dart_tool" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/.pub" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/build" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_linux/.dart_tool" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_linux/.pub" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_linux/build" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/jni/.dart_tool" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/jni/.pub" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/jni/build" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/jni/example/.dart_tool" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/jni/example/.pub" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/jni/example/build" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/.dart_tool" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/.pub" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/build" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/example/.dart_tool" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/example/.pub" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux/example/build" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/.dart_tool" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/.pub" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/build" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/example/.dart_tool" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/example/.pub" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/flutter_secure_storage_windows/example/build" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/jni/.dart_tool" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/jni/.pub" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/jni/build" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/jni/example/.dart_tool" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/jni/example/.pub" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/jni/example/build" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/.dart_tool" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/.pub" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/build" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/example/.dart_tool" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/example/.pub" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/path_provider_windows/example/build" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/.dart_tool" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/.pub" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/build" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/example/.dart_tool" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/example/.pub" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/mobile/windows/flutter/ephemeral/.plugin_symlinks/permission_handler_windows/example/build" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
Generated
-1670
File diff suppressed because it is too large
Load Diff
Generated
-11
@@ -1,11 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="deploymentTargetSelector">
|
|
||||||
<selectionStates>
|
|
||||||
<SelectionState runConfigName="Wear App">
|
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
|
||||||
<DialogSelection />
|
|
||||||
</SelectionState>
|
|
||||||
</selectionStates>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-8
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="MarkdownSettings">
|
|
||||||
<option name="previewPanelProviderInfo">
|
|
||||||
<ProviderInfo name="Compose (experimental)" className="com.intellij.markdown.compose.preview.ComposePanelProvider" />
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-5
@@ -1,5 +0,0 @@
|
|||||||
<project version="4">
|
|
||||||
<component name="ProjectRootManager" version="2">
|
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-8
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectModuleManager">
|
|
||||||
<modules>
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/CESIZen.iml" filepath="$PROJECT_DIR$/.idea/CESIZen.iml" />
|
|
||||||
</modules>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="Launch All (Mobile & Wear)" type="CompoundRunConfigurationType">
|
|
||||||
<toRun name="Mobile App" type="FlutterRunConfigurationType" />
|
|
||||||
<toRun name="Wear App" type="AndroidRunConfigurationType" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
Generated
-7
@@ -1,7 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="Mobile App" type="FlutterRunConfigurationType" factoryName="Flutter">
|
|
||||||
<option name="filePath" value="$PROJECT_DIR$/mobile/lib/main.dart" />
|
|
||||||
<option name="additionalArgs" value="-d PV5HRKVCJVKZ9DOB" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
Generated
-68
@@ -1,68 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="Wear App" type="AndroidRunConfigurationType" factoryName="Android App">
|
|
||||||
<module name="CESIZen.Wear.app.main" />
|
|
||||||
<option name="DEPLOY" value="true" />
|
|
||||||
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
|
|
||||||
<option name="DEPLOY_AS_INSTANT" value="false" />
|
|
||||||
<option name="ARTIFACT_NAME" value="" />
|
|
||||||
<option name="PM_INSTALL_OPTIONS" value="" />
|
|
||||||
<option name="ALL_USERS" value="false" />
|
|
||||||
<option name="ALWAYS_INSTALL_WITH_PM" value="false" />
|
|
||||||
<option name="CLEAR_APP_STORAGE" value="false" />
|
|
||||||
<option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" />
|
|
||||||
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
|
|
||||||
<option name="MODE" value="default_activity" />
|
|
||||||
<option name="CLEAR_LOGCAT" value="false" />
|
|
||||||
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
|
|
||||||
<option name="INSPECTION_WITHOUT_ACTIVITY_RESTART" value="false" />
|
|
||||||
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
|
|
||||||
<option name="PREFERRED_AVD" value="" />
|
|
||||||
<option name="SELECTED_DEVICE_ID" value="adb-d76a0274-X63ZH9 (2)._adb-tls-connect._tcp" />
|
|
||||||
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
|
|
||||||
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
|
|
||||||
<option name="DEBUGGER_TYPE" value="Auto" />
|
|
||||||
<Auto>
|
|
||||||
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
|
||||||
<option name="SHOW_STATIC_VARS" value="true" />
|
|
||||||
<option name="WORKING_DIR" value="" />
|
|
||||||
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
|
||||||
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
|
||||||
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
|
||||||
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
|
||||||
</Auto>
|
|
||||||
<Hybrid>
|
|
||||||
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
|
||||||
<option name="SHOW_STATIC_VARS" value="true" />
|
|
||||||
<option name="WORKING_DIR" value="" />
|
|
||||||
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
|
||||||
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
|
||||||
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
|
||||||
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
|
||||||
</Hybrid>
|
|
||||||
<Java>
|
|
||||||
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
|
||||||
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
|
||||||
</Java>
|
|
||||||
<Native>
|
|
||||||
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
|
|
||||||
<option name="SHOW_STATIC_VARS" value="true" />
|
|
||||||
<option name="WORKING_DIR" value="" />
|
|
||||||
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
|
|
||||||
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
|
|
||||||
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
|
|
||||||
<option name="DEBUG_SANDBOX_SDK" value="false" />
|
|
||||||
</Native>
|
|
||||||
<Profilers>
|
|
||||||
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
|
|
||||||
<option name="STARTUP_PROFILING_ENABLED" value="false" />
|
|
||||||
<option name="DETAILED_REPORT_APP_STARTUP_PROFILING_ENABLED" value="false" />
|
|
||||||
</Profilers>
|
|
||||||
<option name="DEEP_LINK" value="" />
|
|
||||||
<option name="ACTIVITY_CLASS" value="" />
|
|
||||||
<option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" />
|
|
||||||
<option name="SKIP_ACTIVITY_VALIDATION" value="false" />
|
|
||||||
<method v="2">
|
|
||||||
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
|
|
||||||
</method>
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
Generated
-6
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,29 @@
|
|||||||
|
# Journal des modifications — CESIZen
|
||||||
|
|
||||||
|
Toutes les évolutions notables du projet sont consignées dans ce fichier.
|
||||||
|
Le format s'appuie sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/)
|
||||||
|
et le versionnage suit [SemVer](https://semver.org/lang/fr/).
|
||||||
|
|
||||||
|
## [Non publié]
|
||||||
|
|
||||||
|
### Ajouté
|
||||||
|
- Page publique de téléchargement des applications mobiles Android / iOS (`/telecharger`).
|
||||||
|
- Système de tickets connecté à Gitea : création, suivi de l'avancement (état, jalon, labels), fil de discussion et réponses depuis l'interface web.
|
||||||
|
- Pipeline d'intégration continue Gitea Actions (tests, Pint, audit de sécurité).
|
||||||
|
- Pipeline de déploiement continu (build image → registre Gitea → webhook Portainer).
|
||||||
|
- Middleware `SecurityHeaders` (HSTS, CSP, X-Frame-Options, etc.).
|
||||||
|
- Modèle de configuration `.env.production.example` durci.
|
||||||
|
- Modèles de tickets Gitea (incident / évolution) et modèle de Pull Request.
|
||||||
|
- Politique de sécurité (`SECURITY.md`).
|
||||||
|
|
||||||
|
### Sécurité
|
||||||
|
- Confiance au reverse proxy (`trustProxies`) pour la détection HTTPS.
|
||||||
|
- Chiffrement des sessions et cookies `Secure` / `HttpOnly` en production.
|
||||||
|
|
||||||
|
## [1.0.0] - 2025-05-20
|
||||||
|
|
||||||
|
### Ajouté
|
||||||
|
- Application Web Laravel (diagnostic de stress, tracker émotionnel, relaxation, informations).
|
||||||
|
- Application mobile Flutter (iOS / Android).
|
||||||
|
- Application Wear OS (Kotlin / Jetpack Compose).
|
||||||
|
- Administration Filament et API REST (Sanctum).
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
# Politique de sécurité — CESIZen
|
||||||
|
|
||||||
|
## Versions supportées
|
||||||
|
|
||||||
|
| Version | Support sécurité |
|
||||||
|
|---------|------------------|
|
||||||
|
| 1.x | ✅ Oui |
|
||||||
|
| < 1.0 | ❌ Non |
|
||||||
|
|
||||||
|
## Signaler une vulnérabilité
|
||||||
|
|
||||||
|
Toute faille de sécurité doit être signalée **de manière privée**, sans
|
||||||
|
divulgation publique préalable :
|
||||||
|
|
||||||
|
1. Ouvrir un ticket **confidentiel** sur le dépôt Gitea, ou
|
||||||
|
2. Contacter l'équipe à `security@cesizen.fr`.
|
||||||
|
|
||||||
|
Merci d'inclure : description, impact, étapes de reproduction et, si possible,
|
||||||
|
une proposition de correctif.
|
||||||
|
|
||||||
|
**Délais d'engagement :**
|
||||||
|
- Accusé de réception : sous 48 h
|
||||||
|
- Évaluation de la gravité (CVSS) : sous 5 jours ouvrés
|
||||||
|
- Correctif pour les failles critiques : sous 15 jours
|
||||||
|
|
||||||
|
## Mesures de sécurité en place
|
||||||
|
|
||||||
|
- Chiffrement des échanges via **HTTPS/TLS** (Let's Encrypt via Nginx Proxy Manager)
|
||||||
|
- Hachage des mots de passe avec **bcrypt** (12 tours)
|
||||||
|
- Authentification API par jetons **Laravel Sanctum**
|
||||||
|
- En-têtes de sécurité HTTP (HSTS, CSP, X-Frame-Options…)
|
||||||
|
- Protection **CSRF** native Laravel sur les formulaires
|
||||||
|
- Requêtes préparées (Eloquent/PDO) contre les **injections SQL**
|
||||||
|
- Audit automatisé des dépendances (`composer audit`, `npm audit`) dans la CI
|
||||||
|
- Chiffrement des données de session et cookies `Secure` / `HttpOnly`
|
||||||
|
|
||||||
|
## Conformité RGPD
|
||||||
|
|
||||||
|
Les données à caractère personnel sont traitées conformément au RGPD :
|
||||||
|
minimisation, droit à l'effacement (suppression de compte), consentement,
|
||||||
|
et anonymisation des données statistiques.
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,5 @@
|
|||||||
|
node_modules
|
||||||
|
vendor
|
||||||
|
public/build
|
||||||
|
.git
|
||||||
|
.env
|
||||||
@@ -62,4 +62,16 @@ AWS_DEFAULT_REGION=us-east-1
|
|||||||
AWS_BUCKET=
|
AWS_BUCKET=
|
||||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||||
|
|
||||||
|
# --- Gitea (tickets créés depuis le formulaire de suggestions du site) ---
|
||||||
|
# Token d'accès Gitea avec le scope "write:issue" (Settings > Applications > Generate Token).
|
||||||
|
GITEA_URL=https://gitea.sam-coffre.duckdns.org
|
||||||
|
GITEA_TOKEN=
|
||||||
|
GITEA_REPO=sam/cesizen
|
||||||
|
|
||||||
|
# --- Applications mobiles (page /telecharger) ---
|
||||||
|
MOBILE_ANDROID_URL=
|
||||||
|
MOBILE_IOS_URL=
|
||||||
|
MOBILE_ANDROID_VERSION=1.0.0
|
||||||
|
MOBILE_IOS_VERSION=1.0.0
|
||||||
|
|
||||||
VITE_APP_NAME="${APP_NAME}"
|
VITE_APP_NAME="${APP_NAME}"
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# ══════════════════════════════════════════════════════════════
|
||||||
|
# CESIZen — Modèle de configuration PRODUCTION
|
||||||
|
# Copier en `.env` sur le serveur puis renseigner les valeurs.
|
||||||
|
# ⚠️ Ne JAMAIS committer le .env réel (secrets, clés, mots de passe).
|
||||||
|
# ══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
APP_NAME=CESIZen
|
||||||
|
APP_ENV=production
|
||||||
|
APP_KEY= # généré via `php artisan key:generate`
|
||||||
|
APP_DEBUG=false # JAMAIS true en production (fuite d'infos)
|
||||||
|
APP_URL=https://cesizen.sam-coffre.duckdns.org
|
||||||
|
|
||||||
|
APP_LOCALE=fr
|
||||||
|
APP_FALLBACK_LOCALE=fr
|
||||||
|
APP_FAKER_LOCALE=fr_FR
|
||||||
|
|
||||||
|
# ── Journalisation ────────────────────────────────────────────
|
||||||
|
LOG_CHANNEL=stack
|
||||||
|
LOG_STACK=daily # rotation quotidienne des logs
|
||||||
|
LOG_LEVEL=warning # on ne loggue pas le debug en prod
|
||||||
|
|
||||||
|
# ── Base de données (serveur MySQL/MariaDB du réseau) ─────────
|
||||||
|
# DB_HOST = IP ou nom d'hôte du serveur MySQL (ex : 192.168.x.x sur le LAN,
|
||||||
|
# ou le nom d'un conteneur si la base est dans la même stack Docker).
|
||||||
|
DB_CONNECTION=mysql
|
||||||
|
DB_HOST=127.0.0.1
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_DATABASE=cesizen
|
||||||
|
DB_USERNAME=cesizen_app
|
||||||
|
DB_PASSWORD=
|
||||||
|
|
||||||
|
# ── Sessions & cookies (durcissement) ─────────────────────────
|
||||||
|
SESSION_DRIVER=database
|
||||||
|
SESSION_LIFETIME=120
|
||||||
|
SESSION_ENCRYPT=true # chiffrement des données de session
|
||||||
|
SESSION_SECURE_COOKIE=true # cookie envoyé uniquement en HTTPS
|
||||||
|
SESSION_HTTP_ONLY=true # cookie inaccessible au JavaScript (anti-XSS)
|
||||||
|
SESSION_SAME_SITE=lax # protection CSRF renforcée
|
||||||
|
|
||||||
|
# ── Sécurité mots de passe ────────────────────────────────────
|
||||||
|
BCRYPT_ROUNDS=12 # coût du hachage bcrypt
|
||||||
|
|
||||||
|
# ── Cache / File d'attente ────────────────────────────────────
|
||||||
|
CACHE_STORE=database
|
||||||
|
QUEUE_CONNECTION=database
|
||||||
|
FILESYSTEM_DISK=local
|
||||||
|
|
||||||
|
# ── Mail (notifications, réinitialisation de mot de passe) ────
|
||||||
|
MAIL_MAILER=smtp
|
||||||
|
MAIL_HOST=
|
||||||
|
MAIL_PORT=587
|
||||||
|
MAIL_USERNAME=
|
||||||
|
MAIL_PASSWORD=
|
||||||
|
MAIL_FROM_ADDRESS="no-reply@cesizen.fr"
|
||||||
|
MAIL_FROM_NAME="${APP_NAME}"
|
||||||
|
|
||||||
|
# ── Gitea (tickets créés depuis le formulaire de suggestions) ─
|
||||||
|
GITEA_URL=https://gitea.sam-coffre.duckdns.org
|
||||||
|
GITEA_TOKEN= # token Gitea avec le scope write:issue
|
||||||
|
GITEA_REPO=sam/cesizen
|
||||||
|
|
||||||
|
# ── Applications mobiles (page /telecharger) ─────────────────
|
||||||
|
# Liens de store (prioritaires) ou dépôt des binaires dans public/downloads/.
|
||||||
|
MOBILE_ANDROID_URL=
|
||||||
|
MOBILE_IOS_URL=
|
||||||
|
MOBILE_ANDROID_VERSION=1.0.0
|
||||||
|
MOBILE_IOS_VERSION=1.0.0
|
||||||
|
|
||||||
|
VITE_APP_NAME="${APP_NAME}"
|
||||||
@@ -18,7 +18,10 @@
|
|||||||
/public/storage
|
/public/storage
|
||||||
/storage/*.key
|
/storage/*.key
|
||||||
/storage/pail
|
/storage/pail
|
||||||
|
/public/downloads/*.apk
|
||||||
|
/public/downloads/*.ipa
|
||||||
/vendor
|
/vendor
|
||||||
Homestead.json
|
Homestead.json
|
||||||
Homestead.yaml
|
Homestead.yaml
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
.idea
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
FROM php:8.3-fpm
|
||||||
|
|
||||||
|
# 1. Installer les dépendances système
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
git \
|
||||||
|
curl \
|
||||||
|
libpng-dev \
|
||||||
|
libonig-dev \
|
||||||
|
libxml2-dev \
|
||||||
|
libicu-dev \
|
||||||
|
libzip-dev \
|
||||||
|
zip \
|
||||||
|
unzip \
|
||||||
|
gnupg
|
||||||
|
|
||||||
|
# 2. Installer Node.js (pour Vite)
|
||||||
|
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
|
||||||
|
apt-get install -y nodejs
|
||||||
|
|
||||||
|
# 3. Nettoyer le cache apt
|
||||||
|
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# 4. Installer les extensions PHP
|
||||||
|
# On ajoute 'zip' et on configure 'intl' correctement
|
||||||
|
RUN docker-php-ext-configure intl && \
|
||||||
|
docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd intl zip
|
||||||
|
|
||||||
|
# 5. Installer Composer
|
||||||
|
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
||||||
|
|
||||||
|
# 6. Définir le répertoire de travail
|
||||||
|
WORKDIR /var/www
|
||||||
|
|
||||||
|
# 7. Copier le projet
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# 8. Installer les dépendances PHP et Node
|
||||||
|
RUN composer install --no-interaction --optimize-autoloader --no-dev
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# 9. Forcer les permissions sur les binaires node_modules
|
||||||
|
RUN chmod -R +x node_modules/.bin/
|
||||||
|
|
||||||
|
# 10. Lancer le build de Vite
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# 11. Fixer les permissions pour Laravel
|
||||||
|
RUN chown -R www-data:www-data /var/www/storage /var/www/bootstrap/cache
|
||||||
|
|
||||||
|
EXPOSE 9000
|
||||||
|
CMD ["php-fpm"]
|
||||||
+37
-53
@@ -1,59 +1,43 @@
|
|||||||
<p align="center"><a href="https://laravel.com" target="_blank"><img src="https://raw.githubusercontent.com/laravel/art/master/logo-lockup/5%20SVG/2%20CMYK/1%20Full%20Color/laravel-logolockup-cmyk-red.svg" width="400" alt="Laravel Logo"></a></p>
|
# 🖥️ CESIZen - Plateforme Web & API
|
||||||
|
|
||||||
<p align="center">
|
Ce répertoire contient le backend Laravel, l'API REST sécurisée par Sanctum, et le tableau de bord d'administration utilisant le Design System de l'État Français (DSFR).
|
||||||
<a href="https://github.com/laravel/framework/actions"><img src="https://github.com/laravel/framework/workflows/tests/badge.svg" alt="Build Status"></a>
|
|
||||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/dt/laravel/framework" alt="Total Downloads"></a>
|
|
||||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/v/laravel/framework" alt="Latest Stable Version"></a>
|
|
||||||
<a href="https://packagist.org/packages/laravel/framework"><img src="https://img.shields.io/packagist/l/laravel/framework" alt="License"></a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
## About Laravel
|
## 🛠️ Guide d'installation
|
||||||
|
|
||||||
Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as:
|
### Prérequis
|
||||||
|
- Docker et Docker Compose
|
||||||
|
- Composer (optionnel si installation via Docker)
|
||||||
|
- Node.js & NPM
|
||||||
|
|
||||||
- [Simple, fast routing engine](https://laravel.com/docs/routing).
|
### Étapes d'installation
|
||||||
- [Powerful dependency injection container](https://laravel.com/docs/container).
|
1. **Cloner le projet** et entrer dans le dossier `WEB`.
|
||||||
- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage.
|
2. **Configuration de l'environnement** :
|
||||||
- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent).
|
```bash
|
||||||
- Database agnostic [schema migrations](https://laravel.com/docs/migrations).
|
cp .env.example .env
|
||||||
- [Robust background job processing](https://laravel.com/docs/queues).
|
```
|
||||||
- [Real-time event broadcasting](https://laravel.com/docs/broadcasting).
|
3. **Lancer les conteneurs Docker** :
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
4. **Installer les dépendances PHP & JS** :
|
||||||
|
```bash
|
||||||
|
docker exec cesizen-app composer install
|
||||||
|
docker exec cesizen-app npm install
|
||||||
|
docker exec cesizen-app npm run build
|
||||||
|
```
|
||||||
|
5. **Initialiser la base de données** :
|
||||||
|
```bash
|
||||||
|
docker exec cesizen-app php artisan key:generate
|
||||||
|
docker exec cesizen-app php artisan migrate --seed
|
||||||
|
```
|
||||||
|
6. **Accès** :
|
||||||
|
- Web : `http://localhost:8090`
|
||||||
|
- API : `http://localhost:8091`
|
||||||
|
|
||||||
Laravel is accessible, powerful, and provides tools required for large, robust applications.
|
---
|
||||||
|
|
||||||
## Learning Laravel
|
## 🏗️ Architecture Technique (API)
|
||||||
|
- **Framework** : Laravel 10 (PHP 8.2)
|
||||||
Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. You can also check out [Laravel Learn](https://laravel.com/learn), where you will be guided through building a modern Laravel application.
|
- **Auth** : Laravel Sanctum (Tokens Bearer)
|
||||||
|
- **Admin** : FilamentPHP / DSFR
|
||||||
If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library.
|
- **Base de données** : MySQL 8.0
|
||||||
|
|
||||||
## Laravel Sponsors
|
|
||||||
|
|
||||||
We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com).
|
|
||||||
|
|
||||||
### Premium Partners
|
|
||||||
|
|
||||||
- **[Vehikl](https://vehikl.com)**
|
|
||||||
- **[Tighten Co.](https://tighten.co)**
|
|
||||||
- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)**
|
|
||||||
- **[64 Robots](https://64robots.com)**
|
|
||||||
- **[Curotec](https://www.curotec.com/services/technologies/laravel)**
|
|
||||||
- **[DevSquad](https://devsquad.com/hire-laravel-developers)**
|
|
||||||
- **[Redberry](https://redberry.international/laravel-development)**
|
|
||||||
- **[Active Logic](https://activelogic.com)**
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
|
|
||||||
|
|
||||||
## Code of Conduct
|
|
||||||
|
|
||||||
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
|
|
||||||
|
|
||||||
## Security Vulnerabilities
|
|
||||||
|
|
||||||
If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
|
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\InformationResource\Pages;
|
||||||
|
use App\Models\Information;
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Tables;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
|
class InformationResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = Information::class;
|
||||||
|
|
||||||
|
protected static ?string $navigationIcon = 'heroicon-o-information-circle';
|
||||||
|
|
||||||
|
protected static ?string $navigationLabel = 'Informations';
|
||||||
|
|
||||||
|
protected static ?string $modelLabel = 'Information';
|
||||||
|
|
||||||
|
public static function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form
|
||||||
|
->schema([
|
||||||
|
Forms\Components\TextInput::make('title')
|
||||||
|
->label('Titre')
|
||||||
|
->required()
|
||||||
|
->maxLength(255),
|
||||||
|
Forms\Components\TextInput::make('category')
|
||||||
|
->label('Catégorie')
|
||||||
|
->required()
|
||||||
|
->maxLength(255)
|
||||||
|
->default('Général'),
|
||||||
|
Forms\Components\RichEditor::make('content')
|
||||||
|
->label('Contenu')
|
||||||
|
->required()
|
||||||
|
->columnSpanFull(),
|
||||||
|
Forms\Components\FileUpload::make('image_url')
|
||||||
|
->label('Image')
|
||||||
|
->image()
|
||||||
|
->disk('public') // Ajoutez ceci pour forcer l'utilisation du disque public
|
||||||
|
->directory('informations')
|
||||||
|
->visibility('public'),
|
||||||
|
Forms\Components\Toggle::make('is_published')
|
||||||
|
->label('Publié')
|
||||||
|
->default(true)
|
||||||
|
->required(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->columns([
|
||||||
|
Tables\Columns\TextColumn::make('title')
|
||||||
|
->label('Titre')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('category')
|
||||||
|
->label('Catégorie')
|
||||||
|
->badge()
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\IconColumn::make('is_published')
|
||||||
|
->label('Publié')
|
||||||
|
->boolean(),
|
||||||
|
Tables\Columns\TextColumn::make('created_at')
|
||||||
|
->label('Créé le')
|
||||||
|
->dateTime()
|
||||||
|
->sortable()
|
||||||
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
])
|
||||||
|
->filters([
|
||||||
|
Tables\Filters\TernaryFilter::make('is_published')
|
||||||
|
->label('Statut de publication')
|
||||||
|
->boolean()
|
||||||
|
->trueLabel('Publiés')
|
||||||
|
->falseLabel('Brouillons')
|
||||||
|
->native(false),
|
||||||
|
])
|
||||||
|
->actions([
|
||||||
|
Tables\Actions\EditAction::make(),
|
||||||
|
Tables\Actions\DeleteAction::make(),
|
||||||
|
])
|
||||||
|
->bulkActions([
|
||||||
|
Tables\Actions\BulkActionGroup::make([
|
||||||
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => Pages\ListInformation::route('/'),
|
||||||
|
'create' => Pages\CreateInformation::route('/create'),
|
||||||
|
'edit' => Pages\EditInformation::route('/{record}/edit'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\InformationResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\InformationResource;
|
||||||
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
|
|
||||||
|
class CreateInformation extends CreateRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = InformationResource::class;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\InformationResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\InformationResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
|
class EditInformation extends EditRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = InformationResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\DeleteAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\InformationResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\InformationResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListInformation extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = InformationResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\CreateAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\RelaxationActivityResource\Pages;
|
||||||
|
use App\Models\RelaxationActivity;
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Tables;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
|
class RelaxationActivityResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = RelaxationActivity::class;
|
||||||
|
|
||||||
|
protected static ?string $navigationIcon = 'heroicon-o-sparkles';
|
||||||
|
|
||||||
|
protected static ?string $navigationLabel = 'Activités de détente';
|
||||||
|
|
||||||
|
protected static ?string $modelLabel = 'Activité de détente';
|
||||||
|
|
||||||
|
public static function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form
|
||||||
|
->schema([
|
||||||
|
Forms\Components\TextInput::make('title')
|
||||||
|
->label('Titre')
|
||||||
|
->required()
|
||||||
|
->maxLength(255),
|
||||||
|
Forms\Components\Select::make('type')
|
||||||
|
->options([
|
||||||
|
'video' => 'Vidéo',
|
||||||
|
'audio' => 'Audio',
|
||||||
|
'article' => 'Article',
|
||||||
|
'exercice' => 'Exercice',
|
||||||
|
])
|
||||||
|
->required(),
|
||||||
|
Forms\Components\TextInput::make('duration')
|
||||||
|
->label('Durée (min)')
|
||||||
|
->numeric(),
|
||||||
|
Forms\Components\TextInput::make('url')
|
||||||
|
->label('Lien / URL')
|
||||||
|
->url()
|
||||||
|
->maxLength(255),
|
||||||
|
Forms\Components\Textarea::make('description')
|
||||||
|
->columnSpanFull(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->columns([
|
||||||
|
Tables\Columns\TextColumn::make('title')
|
||||||
|
->label('Titre')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('type')
|
||||||
|
->badge(),
|
||||||
|
Tables\Columns\TextColumn::make('duration')
|
||||||
|
->label('Durée')
|
||||||
|
->suffix(' min'),
|
||||||
|
Tables\Columns\TextColumn::make('created_at')
|
||||||
|
->dateTime()
|
||||||
|
->sortable()
|
||||||
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
])
|
||||||
|
->filters([
|
||||||
|
Tables\Filters\SelectFilter::make('type')
|
||||||
|
->options([
|
||||||
|
'video' => 'Vidéo',
|
||||||
|
'audio' => 'Audio',
|
||||||
|
'article' => 'Article',
|
||||||
|
'exercice' => 'Exercice',
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
->actions([
|
||||||
|
Tables\Actions\EditAction::make(),
|
||||||
|
Tables\Actions\DeleteAction::make(),
|
||||||
|
])
|
||||||
|
->bulkActions([
|
||||||
|
Tables\Actions\BulkActionGroup::make([
|
||||||
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRelations(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => Pages\ListRelaxationActivities::route('/'),
|
||||||
|
'create' => Pages\CreateRelaxationActivity::route('/create'),
|
||||||
|
'edit' => Pages\EditRelaxationActivity::route('/{record}/edit'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\RelaxationActivityResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\RelaxationActivityResource;
|
||||||
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
|
|
||||||
|
class CreateRelaxationActivity extends CreateRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = RelaxationActivityResource::class;
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\RelaxationActivityResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\RelaxationActivityResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
|
class EditRelaxationActivity extends EditRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = RelaxationActivityResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\DeleteAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\RelaxationActivityResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\RelaxationActivityResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListRelaxationActivities extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = RelaxationActivityResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\CreateAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\StressEventResource\Pages;
|
||||||
|
use App\Models\StressEvent;
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Tables;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
|
class StressEventResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = StressEvent::class;
|
||||||
|
|
||||||
|
protected static ?string $navigationIcon = 'heroicon-o-bolt';
|
||||||
|
|
||||||
|
protected static ?string $navigationLabel = 'Événements de stress';
|
||||||
|
|
||||||
|
protected static ?string $modelLabel = 'Événement de stress';
|
||||||
|
|
||||||
|
protected static ?string $pluralModelLabel = 'Événements de stress';
|
||||||
|
|
||||||
|
public static function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form
|
||||||
|
->schema([
|
||||||
|
Forms\Components\TextInput::make('event_name')
|
||||||
|
->label('Nom de l\'événement')
|
||||||
|
->required()
|
||||||
|
->maxLength(255),
|
||||||
|
Forms\Components\TextInput::make('points')
|
||||||
|
->label('Points de stress')
|
||||||
|
->required()
|
||||||
|
->numeric()
|
||||||
|
->minValue(0)
|
||||||
|
->maxValue(100),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->columns([
|
||||||
|
Tables\Columns\TextColumn::make('event_name')
|
||||||
|
->label('Événement')
|
||||||
|
->searchable()
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\TextColumn::make('points')
|
||||||
|
->label('Points')
|
||||||
|
->numeric()
|
||||||
|
->sortable(),
|
||||||
|
Tables\Columns\TextColumn::make('updated_at')
|
||||||
|
->label('Dernière modification')
|
||||||
|
->dateTime()
|
||||||
|
->sortable()
|
||||||
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
])
|
||||||
|
->filters([
|
||||||
|
//
|
||||||
|
])
|
||||||
|
->actions([
|
||||||
|
Tables\Actions\EditAction::make(),
|
||||||
|
Tables\Actions\DeleteAction::make(),
|
||||||
|
])
|
||||||
|
->bulkActions([
|
||||||
|
Tables\Actions\BulkActionGroup::make([
|
||||||
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRelations(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => Pages\ListStressEvents::route('/'),
|
||||||
|
'create' => Pages\CreateStressEvent::route('/create'),
|
||||||
|
'edit' => Pages\EditStressEvent::route('/{record}/edit'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\StressEventResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\StressEventResource;
|
||||||
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
|
|
||||||
|
class CreateStressEvent extends CreateRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = StressEventResource::class;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\StressEventResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\StressEventResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
|
class EditStressEvent extends EditRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = StressEventResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\DeleteAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\StressEventResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\StressEventResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListStressEvents extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = StressEventResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\CreateAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\UserResource\Pages;
|
||||||
|
use App\Models\User;
|
||||||
|
use Filament\Forms;
|
||||||
|
use Filament\Forms\Form;
|
||||||
|
use Filament\Resources\Resource;
|
||||||
|
use Filament\Tables;
|
||||||
|
use Filament\Tables\Table;
|
||||||
|
|
||||||
|
class UserResource extends Resource
|
||||||
|
{
|
||||||
|
protected static ?string $model = User::class;
|
||||||
|
|
||||||
|
protected static ?string $navigationIcon = 'heroicon-o-users';
|
||||||
|
|
||||||
|
protected static ?string $navigationLabel = 'Utilisateurs';
|
||||||
|
|
||||||
|
protected static ?string $modelLabel = 'Utilisateur';
|
||||||
|
|
||||||
|
public static function form(Form $form): Form
|
||||||
|
{
|
||||||
|
return $form
|
||||||
|
->schema([
|
||||||
|
Forms\Components\TextInput::make('name')
|
||||||
|
->required()
|
||||||
|
->maxLength(255),
|
||||||
|
Forms\Components\TextInput::make('email')
|
||||||
|
->email()
|
||||||
|
->required()
|
||||||
|
->maxLength(255),
|
||||||
|
Forms\Components\Select::make('id_role')
|
||||||
|
->relationship('role', 'libelle')
|
||||||
|
->required(),
|
||||||
|
Forms\Components\TextInput::make('password')
|
||||||
|
->password()
|
||||||
|
->dehydrated(fn ($state) => filled($state))
|
||||||
|
->required(fn (string $context): bool => $context === 'create'),
|
||||||
|
Forms\Components\Toggle::make('is_active')
|
||||||
|
->label('Compte actif')
|
||||||
|
->default(true)
|
||||||
|
->required(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function table(Table $table): Table
|
||||||
|
{
|
||||||
|
return $table
|
||||||
|
->columns([
|
||||||
|
Tables\Columns\TextColumn::make('name')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('email')
|
||||||
|
->searchable(),
|
||||||
|
Tables\Columns\TextColumn::make('role.libelle')
|
||||||
|
->label('Rôle')
|
||||||
|
->badge()
|
||||||
|
->color(fn (string $state): string => match ($state) {
|
||||||
|
'Admin' => 'danger',
|
||||||
|
'Utilisateur' => 'info',
|
||||||
|
default => 'gray',
|
||||||
|
}),
|
||||||
|
Tables\Columns\ToggleColumn::make('is_active')
|
||||||
|
->label('Actif'),
|
||||||
|
Tables\Columns\TextColumn::make('created_at')
|
||||||
|
->dateTime()
|
||||||
|
->sortable()
|
||||||
|
->toggleable(isToggledHiddenByDefault: true),
|
||||||
|
])
|
||||||
|
->filters([
|
||||||
|
Tables\Filters\TernaryFilter::make('is_active')
|
||||||
|
->label('Statut')
|
||||||
|
->boolean()
|
||||||
|
->trueLabel('Utilisateurs actifs')
|
||||||
|
->falseLabel('Utilisateurs désactivés')
|
||||||
|
->native(false),
|
||||||
|
])
|
||||||
|
->actions([
|
||||||
|
Tables\Actions\EditAction::make(),
|
||||||
|
])
|
||||||
|
->bulkActions([
|
||||||
|
Tables\Actions\BulkActionGroup::make([
|
||||||
|
Tables\Actions\DeleteBulkAction::make(),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getRelations(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'index' => Pages\ListUsers::route('/'),
|
||||||
|
'create' => Pages\CreateUser::route('/create'),
|
||||||
|
'edit' => Pages\EditUser::route('/{record}/edit'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\UserResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\UserResource;
|
||||||
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
|
|
||||||
|
class CreateUser extends CreateRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = UserResource::class;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\UserResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\UserResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\EditRecord;
|
||||||
|
|
||||||
|
class EditUser extends EditRecord
|
||||||
|
{
|
||||||
|
protected static string $resource = UserResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\DeleteAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Filament\Admin\Resources\UserResource\Pages;
|
||||||
|
|
||||||
|
use App\Filament\Admin\Resources\UserResource;
|
||||||
|
use Filament\Actions;
|
||||||
|
use Filament\Resources\Pages\ListRecords;
|
||||||
|
|
||||||
|
class ListUsers extends ListRecords
|
||||||
|
{
|
||||||
|
protected static string $resource = UserResource::class;
|
||||||
|
|
||||||
|
protected function getHeaderActions(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
Actions\CreateAction::make(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,10 +13,11 @@ use Illuminate\Validation\Rule;
|
|||||||
class AuthController extends Controller
|
class AuthController extends Controller
|
||||||
{
|
{
|
||||||
// AuthController.php
|
// AuthController.php
|
||||||
public function login(Request $request) {
|
public function login(Request $request)
|
||||||
|
{
|
||||||
$fields = $request->validate([
|
$fields = $request->validate([
|
||||||
'email' => 'required|string|email',
|
'email' => 'required|string|email',
|
||||||
'password' => 'required|string'
|
'password' => 'required|string',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Log::info('Tentative de connexion', $request->all());
|
// Log::info('Tentative de connexion', $request->all());
|
||||||
@@ -24,13 +25,21 @@ class AuthController extends Controller
|
|||||||
$user = User::where('email', $fields['email'])->first();
|
$user = User::where('email', $fields['email'])->first();
|
||||||
|
|
||||||
// Vérification du mot de passe haché (Sécurité)
|
// Vérification du mot de passe haché (Sécurité)
|
||||||
if(!$user || !Hash::check($fields['password'], $user->password)) {
|
if (! $user || ! Hash::check($fields['password'], $user->password)) {
|
||||||
return response([
|
return response([
|
||||||
'status' => 'error',
|
'status' => 'error',
|
||||||
'message' => 'Identifiants invalides'
|
'message' => 'Identifiants invalides',
|
||||||
], 401);
|
], 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Vérification si le compte est actif
|
||||||
|
if (! $user->is_active) {
|
||||||
|
return response([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Votre compte a été désactivé par un administrateur.',
|
||||||
|
], 403);
|
||||||
|
}
|
||||||
|
|
||||||
// Log::info('Email reçu: ' . $request->email);
|
// Log::info('Email reçu: ' . $request->email);
|
||||||
// Log::info('Password saisi: ' . $request->password);
|
// Log::info('Password saisi: ' . $request->password);
|
||||||
// Log::info('Hachage en BDD: ' . $user->password);
|
// Log::info('Hachage en BDD: ' . $user->password);
|
||||||
@@ -41,7 +50,7 @@ class AuthController extends Controller
|
|||||||
return response([
|
return response([
|
||||||
'status' => 'success',
|
'status' => 'success',
|
||||||
'user' => $user,
|
'user' => $user,
|
||||||
'token' => $token
|
'token' => $token,
|
||||||
], 200);
|
], 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,14 +63,14 @@ class AuthController extends Controller
|
|||||||
'password' => 'required|string|min:8|confirmed',
|
'password' => 'required|string|min:8|confirmed',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
Log::info('Tentative de création', $request->all());
|
Log::info('Tentative de création', $request->all());
|
||||||
|
|
||||||
if ($validator->fails()) {
|
if ($validator->fails()) {
|
||||||
Log::info( $validator->errors());
|
Log::info($validator->errors());
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'status' => 'error',
|
'status' => 'error',
|
||||||
'errors' => $validator->errors()
|
'errors' => $validator->errors(),
|
||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +88,7 @@ class AuthController extends Controller
|
|||||||
'status' => 'success',
|
'status' => 'success',
|
||||||
'message' => 'Compte créé avec succès',
|
'message' => 'Compte créé avec succès',
|
||||||
'user' => $user,
|
'user' => $user,
|
||||||
'token' => $token
|
'token' => $token,
|
||||||
], 201);
|
], 201);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,15 +101,15 @@ class AuthController extends Controller
|
|||||||
'email' => [
|
'email' => [
|
||||||
'sometimes',
|
'sometimes',
|
||||||
'email',
|
'email',
|
||||||
Rule::unique('users')->ignore($user->id) // Empêche les doublons en BDD
|
Rule::unique('users')->ignore($user->id), // Empêche les doublons en BDD
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 2. Vérification de sécurité (RGPD/Confidentialité)
|
// 2. Vérification de sécurité (RGPD/Confidentialité)
|
||||||
if (!Hash::check($request->current_password, $user->password)) {
|
if (! Hash::check($request->current_password, $user->password)) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'status' => 'error',
|
'status' => 'error',
|
||||||
'message' => 'Le mot de passe actuel est incorrect'
|
'message' => 'Le mot de passe actuel est incorrect',
|
||||||
], 403);
|
], 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +120,7 @@ class AuthController extends Controller
|
|||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'status' => 'success',
|
'status' => 'success',
|
||||||
'user' => $user
|
'user' => $user,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,6 @@ namespace App\Http\Controllers\Api;
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use App\Models\EmotionRecord;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class EmotionController extends Controller
|
class EmotionController extends Controller
|
||||||
@@ -18,7 +17,7 @@ class EmotionController extends Controller
|
|||||||
{
|
{
|
||||||
Log::info('Tentative d\'enregistrement d\'émotion', [
|
Log::info('Tentative d\'enregistrement d\'émotion', [
|
||||||
'user' => $request->user()->id,
|
'user' => $request->user()->id,
|
||||||
'data' => $request->all()
|
'data' => $request->all(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -35,7 +34,8 @@ class EmotionController extends Controller
|
|||||||
|
|
||||||
return response()->json($record, 201);
|
return response()->json($record, 201);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::error('Erreur enregistrement émotion : ' . $e->getMessage());
|
Log::error('Erreur enregistrement émotion : '.$e->getMessage());
|
||||||
|
|
||||||
return response()->json(['error' => 'Erreur serveur'], 500);
|
return response()->json(['error' => 'Erreur serveur'], 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Information;
|
||||||
|
|
||||||
|
class InformationController extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$informations = Information::where('is_published', true)
|
||||||
|
->orderBy('created_at', 'desc')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => $informations,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show($id)
|
||||||
|
{
|
||||||
|
$information = Information::where('is_published', true)->find($id);
|
||||||
|
|
||||||
|
if (! $information) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Information non trouvée',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'data' => $information,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ namespace App\Http\Controllers\Api;
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\StressEvent;
|
use App\Models\StressEvent;
|
||||||
use Illuminate\Http\Request;
|
|
||||||
|
|
||||||
class StressDiagnosticController extends Controller
|
class StressDiagnosticController extends Controller
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
class DashboardController extends Controller
|
class DashboardController extends Controller
|
||||||
@@ -17,7 +15,7 @@ class DashboardController extends Controller
|
|||||||
$stats = [
|
$stats = [
|
||||||
'relaxation_time' => '45 min',
|
'relaxation_time' => '45 min',
|
||||||
'stress_status' => 'Stable',
|
'stress_status' => 'Stable',
|
||||||
'last_diagnostic' => 'il y a 3 jours'
|
'last_diagnostic' => 'il y a 3 jours',
|
||||||
];
|
];
|
||||||
|
|
||||||
return view('welcome', compact('user', 'stats'));
|
return view('welcome', compact('user', 'stats'));
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ class AuthController extends Controller
|
|||||||
|
|
||||||
if (Auth::attempt($credentials)) {
|
if (Auth::attempt($credentials)) {
|
||||||
$request->session()->regenerate();
|
$request->session()->regenerate();
|
||||||
|
|
||||||
return redirect()->intended('/');
|
return redirect()->intended('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +62,7 @@ class AuthController extends Controller
|
|||||||
Auth::logout();
|
Auth::logout();
|
||||||
$request->session()->invalidate();
|
$request->session()->invalidate();
|
||||||
$request->session()->regenerateToken();
|
$request->session()->regenerateToken();
|
||||||
|
|
||||||
return redirect('/login');
|
return redirect('/login');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
namespace App\Http\Controllers\Web;
|
namespace App\Http\Controllers\Web;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\ResultatDiag;
|
||||||
use App\Models\StressEvent;
|
use App\Models\StressEvent;
|
||||||
|
use App\Services\StressCalculator;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
@@ -14,40 +16,38 @@ class DiagnosticController extends Controller
|
|||||||
$events = StressEvent::all();
|
$events = StressEvent::all();
|
||||||
$history = [];
|
$history = [];
|
||||||
if (Auth::check()) {
|
if (Auth::check()) {
|
||||||
$history = \App\Models\ResultatDiag::where('id_user', Auth::id())
|
$history = ResultatDiag::where('id_user', Auth::id())
|
||||||
->orderBy('date_passage', 'desc')
|
->orderBy('date_passage', 'desc')
|
||||||
->take(5)
|
->take(5)
|
||||||
->get();
|
->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('diagnostics.index', compact('events', 'history'));
|
return view('diagnostics.index', compact('events', 'history'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function history()
|
public function history()
|
||||||
{
|
{
|
||||||
$history = \App\Models\ResultatDiag::where('id_user', Auth::id())
|
$history = ResultatDiag::where('id_user', Auth::id())
|
||||||
->orderBy('date_passage', 'desc')
|
->orderBy('date_passage', 'desc')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
return view('diagnostics.history', compact('history'));
|
return view('diagnostics.history', compact('history'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function store(Request $request)
|
public function store(Request $request, StressCalculator $calculator)
|
||||||
{
|
{
|
||||||
// Logique pour calculer le score Holmes-Rahe
|
// Logique pour calculer le score Holmes-Rahe
|
||||||
$totalPoints = 0;
|
$totalPoints = 0;
|
||||||
if ($request->has('events')) {
|
if ($request->has('events')) {
|
||||||
$totalPoints = StressEvent::whereIn('id', $request->events)->sum('points');
|
$points = StressEvent::whereIn('id', $request->events)->pluck('points')->toArray();
|
||||||
|
$totalPoints = $calculator->calculateScore($points);
|
||||||
}
|
}
|
||||||
|
|
||||||
$niveauStress = 'Faible';
|
$niveauStress = $calculator->determineLevel($totalPoints);
|
||||||
if ($totalPoints >= 300) {
|
|
||||||
$niveauStress = 'Élevé';
|
|
||||||
} elseif ($totalPoints >= 150) {
|
|
||||||
$niveauStress = 'Modéré';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enregistrement si l'utilisateur est connecté
|
// Enregistrement si l'utilisateur est connecté
|
||||||
if (Auth::check()) {
|
if (Auth::check()) {
|
||||||
\App\Models\ResultatDiag::create([
|
ResultatDiag::create([
|
||||||
'id_user' => Auth::id(),
|
'id_user' => Auth::id(),
|
||||||
'score_total' => $totalPoints,
|
'score_total' => $totalPoints,
|
||||||
'niveau_stress' => $niveauStress,
|
'niveau_stress' => $niveauStress,
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Web;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
|
||||||
|
class DownloadController extends Controller
|
||||||
|
{
|
||||||
|
// Page publique de téléchargement des applications mobiles
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return view('telecharger', [
|
||||||
|
'android' => $this->resoudre('android'),
|
||||||
|
'ios' => $this->resoudre('ios'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Détermine, pour une plateforme, le lien de téléchargement et sa disponibilité.
|
||||||
|
*
|
||||||
|
* @return array{version: string, url: string|null, disponible: bool}
|
||||||
|
*/
|
||||||
|
private function resoudre(string $plateforme): array
|
||||||
|
{
|
||||||
|
$config = config("mobile.{$plateforme}");
|
||||||
|
if (! is_array($config)) {
|
||||||
|
$config = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$version = is_string($config['version'] ?? null) ? $config['version'] : '1.0.0';
|
||||||
|
$url = (is_string($config['url'] ?? null) && $config['url'] !== '') ? $config['url'] : null;
|
||||||
|
$file = is_string($config['file'] ?? null) ? $config['file'] : null;
|
||||||
|
|
||||||
|
// À défaut d'URL explicite, on propose le fichier précompilé s'il existe.
|
||||||
|
if ($url === null && $file !== null && file_exists(public_path($file))) {
|
||||||
|
$url = asset($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'version' => $version,
|
||||||
|
'url' => $url,
|
||||||
|
'disponible' => $url !== null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ class EmotionController extends Controller
|
|||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$records = EmotionRecord::where('user_id', Auth::id())->latest()->get();
|
$records = EmotionRecord::where('user_id', Auth::id())->latest()->get();
|
||||||
|
|
||||||
return view('emotions.index', compact('records'));
|
return view('emotions.index', compact('records'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,14 +21,14 @@ class EmotionController extends Controller
|
|||||||
$request->validate([
|
$request->validate([
|
||||||
'emotion' => 'required|string',
|
'emotion' => 'required|string',
|
||||||
'intensity' => 'required|integer|min:1|max:5',
|
'intensity' => 'required|integer|min:1|max:5',
|
||||||
'note' => 'nullable|string'
|
'note' => 'nullable|string',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
EmotionRecord::create([
|
EmotionRecord::create([
|
||||||
'user_id' => Auth::id(),
|
'user_id' => Auth::id(),
|
||||||
'emotion' => $request->emotion,
|
'emotion' => $request->emotion,
|
||||||
'intensity' => $request->intensity,
|
'intensity' => $request->intensity,
|
||||||
'note' => $request->note
|
'note' => $request->note,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return redirect()->back()->with('success', 'Émotion enregistrée');
|
return redirect()->back()->with('success', 'Émotion enregistrée');
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Web;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Information;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class InformationController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$query = Information::where('is_published', true);
|
||||||
|
|
||||||
|
if ($request->has('category') && $request->category != '') {
|
||||||
|
$query->where('category', $request->category);
|
||||||
|
}
|
||||||
|
|
||||||
|
$informations = $query->orderBy('created_at', 'desc')
|
||||||
|
->paginate(6)
|
||||||
|
->withQueryString();
|
||||||
|
|
||||||
|
$categories = Information::where('is_published', true)
|
||||||
|
->distinct()
|
||||||
|
->pluck('category');
|
||||||
|
|
||||||
|
return view('informations.index', compact('informations', 'categories'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show($id)
|
||||||
|
{
|
||||||
|
$information = Information::where('is_published', true)->findOrFail($id);
|
||||||
|
|
||||||
|
return view('informations.show', compact('information'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ class ProfileController extends Controller
|
|||||||
|
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'name' => ['required', 'string', 'max:255'],
|
'name' => ['required', 'string', 'max:255'],
|
||||||
'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email,' . $user->id],
|
'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email,'.$user->id],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user->fill($validated);
|
$user->fill($validated);
|
||||||
|
|||||||
@@ -4,17 +4,25 @@ namespace App\Http\Controllers\Web;
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\RelaxationActivity;
|
use App\Models\RelaxationActivity;
|
||||||
use App\Models\Favorite;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
|
|
||||||
class RelaxationController extends Controller
|
class RelaxationController extends Controller
|
||||||
{
|
{
|
||||||
public function index()
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$activities = RelaxationActivity::all();
|
$query = RelaxationActivity::query();
|
||||||
$favorites = [];
|
|
||||||
return view('relaxation.index', compact('activities', 'favorites'));
|
if ($request->has('category') && $request->category != '') {
|
||||||
|
$query->where('category', $request->category);
|
||||||
|
}
|
||||||
|
|
||||||
|
$activities = $query->get();
|
||||||
|
|
||||||
|
$categories = RelaxationActivity::distinct()->pluck('category');
|
||||||
|
|
||||||
|
$favorites = []; // TODO: Implémenter la logique des favoris si nécessaire
|
||||||
|
|
||||||
|
return view('relaxation.index', compact('activities', 'favorites', 'categories'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toggleFavorite($id)
|
public function toggleFavorite($id)
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Web;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Ticket;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Services\GiteaIssueService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class TicketController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(private GiteaIssueService $gitea) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Liste des tickets de l'utilisateur, avec leur état courant récupéré
|
||||||
|
* depuis Gitea (ouvert / résolu, jalon).
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
/** @var User $user */
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
$tickets = $user->tickets()->latest()->get()->map(function (Ticket $ticket) {
|
||||||
|
$issue = $this->gitea->getIssue($ticket->gitea_number);
|
||||||
|
$ticket->setAttribute('state', $issue['state'] ?? null);
|
||||||
|
$ticket->setAttribute('milestone', $issue['milestone']['title'] ?? null);
|
||||||
|
|
||||||
|
return $ticket;
|
||||||
|
});
|
||||||
|
|
||||||
|
return view('tickets.index', compact('tickets'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formulaire de création
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
return view('tickets.create');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Création d'un ticket -> issue Gitea + enregistrement local
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$data = $request->validate([
|
||||||
|
'type' => ['required', Rule::in(['Modification', 'Ajout', 'Correction'])],
|
||||||
|
'titre' => ['required', 'string', 'min:5', 'max:120'],
|
||||||
|
'description' => ['required', 'string', 'min:10', 'max:5000'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** @var User $user */
|
||||||
|
$user = Auth::user();
|
||||||
|
$auteur = $user->name ?? 'Utilisateur';
|
||||||
|
|
||||||
|
$title = "[{$data['type']}] {$data['titre']}";
|
||||||
|
$body = "**Type de demande :** {$data['type']}\n"
|
||||||
|
."**Proposé par :** {$auteur} (via le site CESIZen)\n\n"
|
||||||
|
."### Description\n"
|
||||||
|
.$data['description']."\n\n"
|
||||||
|
."---\n"
|
||||||
|
.'_Ticket créé automatiquement depuis CESIZen._';
|
||||||
|
|
||||||
|
$issue = $this->gitea->createIssue($title, $body);
|
||||||
|
|
||||||
|
if (! $issue || ! isset($issue['number'])) {
|
||||||
|
return back()->withInput()->withErrors([
|
||||||
|
'gitea' => "L'envoi de votre demande a échoué. Merci de réessayer plus tard.",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// On mémorise le lien utilisateur <-> issue pour le suivi.
|
||||||
|
$user->tickets()->create([
|
||||||
|
'gitea_number' => (int) $issue['number'],
|
||||||
|
'title' => $data['titre'],
|
||||||
|
'type' => $data['type'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
return redirect()->route('tickets.show', $issue['number'])
|
||||||
|
->with('success', 'Votre demande a bien été transmise à l\'équipe.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Détail d'un ticket : état, jalon, labels et fil de discussion.
|
||||||
|
*/
|
||||||
|
public function show(int $number)
|
||||||
|
{
|
||||||
|
$ticket = $this->findOwnedTicket($number);
|
||||||
|
|
||||||
|
$issue = $this->gitea->getIssue($number);
|
||||||
|
$comments = $this->gitea->getComments($number);
|
||||||
|
|
||||||
|
return view('tickets.show', compact('ticket', 'issue', 'comments'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Réponse de l'utilisateur : ajoute un commentaire sur l'issue Gitea.
|
||||||
|
*/
|
||||||
|
public function reply(Request $request, int $number)
|
||||||
|
{
|
||||||
|
$this->findOwnedTicket($number);
|
||||||
|
|
||||||
|
$data = $request->validate([
|
||||||
|
'message' => ['required', 'string', 'min:2', 'max:5000'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** @var User $user */
|
||||||
|
$user = Auth::user();
|
||||||
|
$auteur = $user->name ?? 'Utilisateur';
|
||||||
|
$body = "**{$auteur}** (via CESIZen) :\n\n".$data['message'];
|
||||||
|
|
||||||
|
if (! $this->gitea->addComment($number, $body)) {
|
||||||
|
return back()->withErrors(['gitea' => "L'envoi de votre réponse a échoué."]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('tickets.show', $number)
|
||||||
|
->with('success', 'Votre réponse a été ajoutée au ticket.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère le ticket s'il appartient bien à l'utilisateur connecté,
|
||||||
|
* sinon renvoie une 404 (empêche de consulter les tickets d'autrui).
|
||||||
|
*/
|
||||||
|
private function findOwnedTicket(int $number): Ticket
|
||||||
|
{
|
||||||
|
/** @var User $user */
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
return $user->tickets()->where('gitea_number', $number)->firstOrFail();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SecurityHeaders
|
||||||
|
* ---------------
|
||||||
|
* Ajoute les en-têtes HTTP de sécurité recommandés (OWASP Secure Headers)
|
||||||
|
* à toutes les réponses de l'application.
|
||||||
|
*
|
||||||
|
* Ces en-têtes renforcent la protection contre :
|
||||||
|
* - le clickjacking (X-Frame-Options)
|
||||||
|
* - le MIME-sniffing (X-Content-Type-Options)
|
||||||
|
* - les fuites de référent (Referrer-Policy)
|
||||||
|
* - le vol de session sur HTTP (Strict-Transport-Security / HSTS)
|
||||||
|
* - certaines injections de contenu (Content-Security-Policy)
|
||||||
|
*/
|
||||||
|
class SecurityHeaders
|
||||||
|
{
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
/** @var Response $response */
|
||||||
|
$response = $next($request);
|
||||||
|
|
||||||
|
// Empêche l'affichage du site dans une iframe (anti-clickjacking)
|
||||||
|
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');
|
||||||
|
|
||||||
|
// Empêche le navigateur de "deviner" le type MIME d'un fichier
|
||||||
|
$response->headers->set('X-Content-Type-Options', 'nosniff');
|
||||||
|
|
||||||
|
// Limite les informations de référent envoyées vers l'extérieur
|
||||||
|
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
|
||||||
|
|
||||||
|
// Désactive des API navigateur sensibles non utilisées par l'app
|
||||||
|
$response->headers->set(
|
||||||
|
'Permissions-Policy',
|
||||||
|
'geolocation=(), microphone=(), camera=(), payment=()'
|
||||||
|
);
|
||||||
|
|
||||||
|
// HSTS : force le navigateur à n'utiliser que HTTPS pendant 1 an.
|
||||||
|
// Uniquement sur connexion sécurisée (le reverse proxy NPM termine le TLS).
|
||||||
|
if ($request->isSecure()) {
|
||||||
|
$response->headers->set(
|
||||||
|
'Strict-Transport-Security',
|
||||||
|
'max-age=31536000; includeSubDomains'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content-Security-Policy : restreint les sources de contenu autorisées.
|
||||||
|
// 'unsafe-inline' est toléré pour les styles/scripts Blade + Filament.
|
||||||
|
$response->headers->set(
|
||||||
|
'Content-Security-Policy',
|
||||||
|
"default-src 'self'; "
|
||||||
|
."img-src 'self' data: https:; "
|
||||||
|
."script-src 'self' 'unsafe-inline' 'unsafe-eval'; "
|
||||||
|
."style-src 'self' 'unsafe-inline' https:; "
|
||||||
|
."font-src 'self' data: https:; "
|
||||||
|
."connect-src 'self'; "
|
||||||
|
."frame-ancestors 'self'"
|
||||||
|
);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Models;
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
|
|
||||||
class EvenementStress extends Model
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Information extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $table = 'information';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'title',
|
||||||
|
'content',
|
||||||
|
'category',
|
||||||
|
'image_url',
|
||||||
|
'is_published',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'is_published' => 'boolean',
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ class ResultatDiag extends Model
|
|||||||
'date_passage',
|
'date_passage',
|
||||||
'score_total',
|
'score_total',
|
||||||
'niveau_stress',
|
'niveau_stress',
|
||||||
'event_ids'
|
'event_ids',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function user()
|
public function user()
|
||||||
@@ -26,8 +26,11 @@ class ResultatDiag extends Model
|
|||||||
|
|
||||||
public function getEventsAttribute()
|
public function getEventsAttribute()
|
||||||
{
|
{
|
||||||
if (!$this->event_ids) return collect();
|
if (! $this->event_ids) {
|
||||||
|
return collect();
|
||||||
|
}
|
||||||
$ids = explode(';', $this->event_ids);
|
$ids = explode(';', $this->event_ids);
|
||||||
|
|
||||||
return StressEvent::whereIn('id', $ids)->get();
|
return StressEvent::whereIn('id', $ids)->get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $id
|
||||||
|
* @property string $libelle
|
||||||
|
*/
|
||||||
class Role extends Model
|
class Role extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|||||||
@@ -2,9 +2,15 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
class StressEvent extends Model
|
class StressEvent extends Model
|
||||||
{
|
{
|
||||||
//
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'event_name',
|
||||||
|
'points',
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $id
|
||||||
|
* @property int $user_id
|
||||||
|
* @property int $gitea_number
|
||||||
|
* @property string $title
|
||||||
|
* @property string $type
|
||||||
|
*/
|
||||||
|
class Ticket extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = [
|
||||||
|
'user_id',
|
||||||
|
'gitea_number',
|
||||||
|
'title',
|
||||||
|
'type',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
+34
-23
@@ -2,61 +2,72 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
use Filament\Models\Contracts\FilamentUser;
|
||||||
use Database\Factories\UserFactory;
|
use Filament\Panel;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
use Laravel\Sanctum\HasApiTokens;
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
|
|
||||||
class User extends Authenticatable
|
/**
|
||||||
|
* @property bool $is_active
|
||||||
|
* @property-read Role|null $role
|
||||||
|
*/
|
||||||
|
class User extends Authenticatable implements FilamentUser
|
||||||
{
|
{
|
||||||
/** @use HasFactory<UserFactory> */
|
|
||||||
use HasFactory, Notifiable;
|
|
||||||
use HasApiTokens, HasFactory, Notifiable;
|
use HasApiTokens, HasFactory, Notifiable;
|
||||||
|
|
||||||
/**
|
|
||||||
* The attributes that are mass assignable.
|
|
||||||
*
|
|
||||||
* @var list<string>
|
|
||||||
*/
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'name',
|
'name',
|
||||||
'email',
|
'email',
|
||||||
'password',
|
'password',
|
||||||
'id_role',
|
'id_role',
|
||||||
|
'is_active',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* The attributes that should be hidden for serialization.
|
|
||||||
*
|
|
||||||
* @var list<string>
|
|
||||||
*/
|
|
||||||
protected $hidden = [
|
protected $hidden = [
|
||||||
'password',
|
'password',
|
||||||
'remember_token',
|
'remember_token',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the attributes that should be cast.
|
|
||||||
*
|
|
||||||
* @return array<string, string>
|
|
||||||
*/
|
|
||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'email_verified_at' => 'datetime',
|
'email_verified_at' => 'datetime',
|
||||||
'password' => 'hashed',
|
'password' => 'hashed',
|
||||||
|
'is_active' => 'boolean',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function emotionRecords()
|
public function role(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->hasMany(EmotionRecord::class);
|
return $this->belongsTo(Role::class, 'id_role');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function favoritedBy()
|
public function emotionRecords(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(EmotionRecord::class, 'user_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return HasMany<Ticket, $this>
|
||||||
|
*/
|
||||||
|
public function tickets(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Ticket::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function favoritedActivities(): BelongsToMany
|
||||||
{
|
{
|
||||||
return $this->belongsToMany(RelaxationActivity::class, 'favorites', 'id_user', 'id_activite');
|
return $this->belongsToMany(RelaxationActivity::class, 'favorites', 'id_user', 'id_activite');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function canAccessPanel(Panel $panel): bool
|
||||||
|
{
|
||||||
|
// Autorise l'accès si l'utilisateur est actif et a le rôle 'Admin'
|
||||||
|
return $this->is_active && $this->role && $this->role->libelle === 'Admin';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\URL;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
@@ -19,6 +20,8 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
//
|
if (config('app.env') === 'production' || config('app.env') === 'local') {
|
||||||
|
URL::forceScheme('https');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers\Filament;
|
||||||
|
|
||||||
|
use Filament\Http\Middleware\Authenticate;
|
||||||
|
use Filament\Http\Middleware\AuthenticateSession;
|
||||||
|
use Filament\Http\Middleware\DisableBladeIconComponents;
|
||||||
|
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
||||||
|
use Filament\Pages;
|
||||||
|
use Filament\Panel;
|
||||||
|
use Filament\PanelProvider;
|
||||||
|
use Filament\Support\Colors\Color;
|
||||||
|
use Filament\Widgets;
|
||||||
|
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
||||||
|
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||||
|
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||||
|
use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||||
|
use Illuminate\Session\Middleware\StartSession;
|
||||||
|
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||||
|
|
||||||
|
class AdminPanelProvider extends PanelProvider
|
||||||
|
{
|
||||||
|
public function panel(Panel $panel): Panel
|
||||||
|
{
|
||||||
|
return $panel
|
||||||
|
->id('admin')
|
||||||
|
->path('admin')
|
||||||
|
->brandName('CESIZen')
|
||||||
|
->homeUrl('/')
|
||||||
|
->colors([
|
||||||
|
'primary' => Color::Amber,
|
||||||
|
])
|
||||||
|
->discoverResources(in: app_path('Filament/Admin/Resources'), for: 'App\\Filament\\Admin\\Resources')
|
||||||
|
->discoverPages(in: app_path('Filament/Admin/Pages'), for: 'App\\Filament\\Admin\\Pages')
|
||||||
|
->pages([
|
||||||
|
Pages\Dashboard::class,
|
||||||
|
])
|
||||||
|
->discoverWidgets(in: app_path('Filament/Admin/Widgets'), for: 'App\\Filament\\Admin\\Widgets')
|
||||||
|
->widgets([
|
||||||
|
Widgets\AccountWidget::class,
|
||||||
|
Widgets\FilamentInfoWidget::class,
|
||||||
|
])
|
||||||
|
->middleware([
|
||||||
|
EncryptCookies::class,
|
||||||
|
AddQueuedCookiesToResponse::class,
|
||||||
|
StartSession::class,
|
||||||
|
AuthenticateSession::class,
|
||||||
|
ShareErrorsFromSession::class,
|
||||||
|
VerifyCsrfToken::class,
|
||||||
|
SubstituteBindings::class,
|
||||||
|
DisableBladeIconComponents::class,
|
||||||
|
DispatchServingFilamentEvent::class,
|
||||||
|
])
|
||||||
|
->authMiddleware([
|
||||||
|
Authenticate::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
use Illuminate\Http\Client\PendingRequest;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Petit client de l'API Gitea (issues) utilisé par le système de tickets :
|
||||||
|
* création, consultation, commentaires.
|
||||||
|
*/
|
||||||
|
class GiteaIssueService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Prépare un client HTTP authentifié, ou null si Gitea n'est pas configuré.
|
||||||
|
*/
|
||||||
|
private function client(): ?PendingRequest
|
||||||
|
{
|
||||||
|
$url = rtrim((string) config('services.gitea.url'), '/');
|
||||||
|
$repo = trim((string) config('services.gitea.repo'), '/');
|
||||||
|
$token = (string) config('services.gitea.token');
|
||||||
|
|
||||||
|
if ($url === '' || $repo === '' || $token === '') {
|
||||||
|
Log::warning('Gitea non configuré (URL / token / dépôt manquant).');
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gitea attend l'en-tête "Authorization: token <TOKEN>".
|
||||||
|
return Http::withToken($token, 'token')
|
||||||
|
->acceptJson()
|
||||||
|
->timeout(10)
|
||||||
|
->baseUrl("{$url}/api/v1/repos/{$repo}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée un ticket (issue). Retourne les données de l'issue créée
|
||||||
|
* (dont "number") ou null en cas d'échec.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>|null
|
||||||
|
*/
|
||||||
|
public function createIssue(string $title, string $body): ?array
|
||||||
|
{
|
||||||
|
$client = $this->client();
|
||||||
|
if (! $client) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = $client->post('/issues', ['title' => $title, 'body' => $body]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::error('Appel API Gitea impossible : '.$e->getMessage());
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($response->failed()) {
|
||||||
|
Log::error('Création du ticket Gitea échouée', ['status' => $response->status()]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (array) $response->json();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère une issue (état, labels, jalon…).
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>|null
|
||||||
|
*/
|
||||||
|
public function getIssue(int $number): ?array
|
||||||
|
{
|
||||||
|
$client = $this->client();
|
||||||
|
if (! $client) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = $client->get("/issues/{$number}");
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::error('Lecture du ticket Gitea impossible : '.$e->getMessage());
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response->successful() ? (array) $response->json() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère les commentaires d'une issue (fil de discussion).
|
||||||
|
*
|
||||||
|
* @return array<int, mixed>
|
||||||
|
*/
|
||||||
|
public function getComments(int $number): array
|
||||||
|
{
|
||||||
|
$client = $this->client();
|
||||||
|
if (! $client) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = $client->get("/issues/{$number}/comments");
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::error('Lecture des commentaires Gitea impossible : '.$e->getMessage());
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response->successful() ? (array) $response->json() : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ajoute un commentaire (réponse) à une issue.
|
||||||
|
*/
|
||||||
|
public function addComment(int $number, string $body): bool
|
||||||
|
{
|
||||||
|
$client = $this->client();
|
||||||
|
if (! $client) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$response = $client->post("/issues/{$number}/comments", ['body' => $body]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::error('Ajout de commentaire Gitea impossible : '.$e->getMessage());
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response->successful();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
class StressCalculator
|
||||||
|
{
|
||||||
|
public const LEVEL_HIGH = 'Élevé';
|
||||||
|
|
||||||
|
public const LEVEL_MODERATE = 'Modéré';
|
||||||
|
|
||||||
|
public const LEVEL_LOW = 'Faible';
|
||||||
|
|
||||||
|
public function calculateScore(array $points): int
|
||||||
|
{
|
||||||
|
return array_sum($points);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function determineLevel(int $score): string
|
||||||
|
{
|
||||||
|
if ($score >= 300) {
|
||||||
|
return self::LEVEL_HIGH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($score >= 150) {
|
||||||
|
return self::LEVEL_MODERATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::LEVEL_LOW;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Middleware\SecurityHeaders;
|
||||||
use Illuminate\Foundation\Application;
|
use Illuminate\Foundation\Application;
|
||||||
use Illuminate\Foundation\Configuration\Exceptions;
|
use Illuminate\Foundation\Configuration\Exceptions;
|
||||||
use Illuminate\Foundation\Configuration\Middleware;
|
use Illuminate\Foundation\Configuration\Middleware;
|
||||||
@@ -12,7 +13,13 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
->withMiddleware(function (Middleware $middleware): void {
|
->withMiddleware(function (Middleware $middleware): void {
|
||||||
//
|
// L'application est servie derrière un reverse proxy (Nginx Proxy Manager)
|
||||||
|
// qui termine le TLS : on lui fait confiance pour détecter HTTPS,
|
||||||
|
// l'IP réelle du client et le bon schéma d'URL.
|
||||||
|
$middleware->trustProxies(at: '*');
|
||||||
|
|
||||||
|
// En-têtes de sécurité HTTP appliqués à toutes les réponses.
|
||||||
|
$middleware->append(SecurityHeaders::class);
|
||||||
})
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions): void {
|
->withExceptions(function (Exceptions $exceptions): void {
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Providers\AppServiceProvider;
|
use App\Providers\AppServiceProvider;
|
||||||
|
use App\Providers\Filament\AdminPanelProvider;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
AppServiceProvider::class,
|
AppServiceProvider::class,
|
||||||
|
AdminPanelProvider::class,
|
||||||
];
|
];
|
||||||
|
|||||||
+11
-2
@@ -8,6 +8,7 @@
|
|||||||
"require": {
|
"require": {
|
||||||
"php": "^8.2",
|
"php": "^8.2",
|
||||||
"blade-ui-kit/blade-heroicons": "^2.7",
|
"blade-ui-kit/blade-heroicons": "^2.7",
|
||||||
|
"filament/filament": "^3.2",
|
||||||
"guzzlehttp/guzzle": "^7.10",
|
"guzzlehttp/guzzle": "^7.10",
|
||||||
"laravel/framework": "^12.0",
|
"laravel/framework": "^12.0",
|
||||||
"laravel/sanctum": "^4.0",
|
"laravel/sanctum": "^4.0",
|
||||||
@@ -15,11 +16,13 @@
|
|||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"fakerphp/faker": "^1.23",
|
"fakerphp/faker": "^1.23",
|
||||||
|
"larastan/larastan": "^3.0",
|
||||||
"laravel/pail": "^1.2.2",
|
"laravel/pail": "^1.2.2",
|
||||||
"laravel/pint": "^1.24",
|
"laravel/pint": "^1.24",
|
||||||
"laravel/sail": "^1.41",
|
"laravel/sail": "^1.41",
|
||||||
"mockery/mockery": "^1.6",
|
"mockery/mockery": "^1.6",
|
||||||
"nunomaduro/collision": "^8.6",
|
"nunomaduro/collision": "^8.6",
|
||||||
|
"phpro/grumphp": "^2.5",
|
||||||
"phpunit/phpunit": "^11.5.50"
|
"phpunit/phpunit": "^11.5.50"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -53,7 +56,8 @@
|
|||||||
],
|
],
|
||||||
"post-autoload-dump": [
|
"post-autoload-dump": [
|
||||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||||
"@php artisan package:discover --ansi"
|
"@php artisan package:discover --ansi",
|
||||||
|
"@php artisan filament:upgrade"
|
||||||
],
|
],
|
||||||
"post-update-cmd": [
|
"post-update-cmd": [
|
||||||
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
|
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
|
||||||
@@ -73,15 +77,20 @@
|
|||||||
"extra": {
|
"extra": {
|
||||||
"laravel": {
|
"laravel": {
|
||||||
"dont-discover": []
|
"dont-discover": []
|
||||||
|
},
|
||||||
|
"grumphp": {
|
||||||
|
"disable-plugin": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"optimize-autoloader": true,
|
"optimize-autoloader": true,
|
||||||
"preferred-install": "dist",
|
"preferred-install": "dist",
|
||||||
"sort-packages": true,
|
"sort-packages": true,
|
||||||
|
"platform-check": false,
|
||||||
"allow-plugins": {
|
"allow-plugins": {
|
||||||
"pestphp/pest-plugin": true,
|
"pestphp/pest-plugin": true,
|
||||||
"php-http/discovery": true
|
"php-http/discovery": true,
|
||||||
|
"phpro/grumphp": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimum-stability": "stable",
|
"minimum-stability": "stable",
|
||||||
|
|||||||
Generated
+4198
-355
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Applications mobiles (téléchargement)
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Pour chaque plateforme, la page de téléchargement utilise en priorité
|
||||||
|
| l'URL fournie (lien de store OU URL directe). À défaut, elle propose le
|
||||||
|
| fichier précompilé déposé dans public/downloads/. Si aucun des deux n'est
|
||||||
|
| disponible, le bouton affiche « bientôt disponible ».
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'android' => [
|
||||||
|
'version' => env('MOBILE_ANDROID_VERSION', '1.0.0'),
|
||||||
|
'url' => env('MOBILE_ANDROID_URL'),
|
||||||
|
'file' => 'downloads/cesizen.apk',
|
||||||
|
],
|
||||||
|
|
||||||
|
'ios' => [
|
||||||
|
'version' => env('MOBILE_IOS_VERSION', '1.0.0'),
|
||||||
|
'url' => env('MOBILE_IOS_URL'),
|
||||||
|
'file' => 'downloads/cesizen.ipa',
|
||||||
|
],
|
||||||
|
|
||||||
|
];
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||||
|
use Illuminate\Foundation\Http\Middleware\ValidateCsrfToken;
|
||||||
|
use Laravel\Sanctum\Http\Middleware\AuthenticateSession;
|
||||||
use Laravel\Sanctum\Sanctum;
|
use Laravel\Sanctum\Sanctum;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@@ -76,9 +79,9 @@ return [
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
'middleware' => [
|
'middleware' => [
|
||||||
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
|
'authenticate_session' => AuthenticateSession::class,
|
||||||
'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
|
'encrypt_cookies' => EncryptCookies::class,
|
||||||
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
|
'validate_csrf_token' => ValidateCsrfToken::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -35,4 +35,11 @@ return [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// Gitea : création de tickets (issues) depuis le formulaire de suggestions.
|
||||||
|
'gitea' => [
|
||||||
|
'url' => env('GITEA_URL', 'https://gitea.sam-coffre.duckdns.org'),
|
||||||
|
'token' => env('GITEA_TOKEN'),
|
||||||
|
'repo' => env('GITEA_REPO', 'sam/cesizen'),
|
||||||
|
],
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace Database\Factories;
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Models\Role;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
@@ -30,6 +31,7 @@ class UserFactory extends Factory
|
|||||||
'email_verified_at' => now(),
|
'email_verified_at' => now(),
|
||||||
'password' => static::$password ??= Hash::make('password'),
|
'password' => static::$password ??= Hash::make('password'),
|
||||||
'remember_token' => Str::random(10),
|
'remember_token' => Str::random(10),
|
||||||
|
'id_role' => Role::first() ?? Role::create(['libelle' => 'Utilisateur']),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,19 @@ use Illuminate\Database\Migrations\Migration;
|
|||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
return new class extends Migration {
|
return new class extends Migration
|
||||||
public function up(): void {
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
Schema::create('roles', function (Blueprint $table) {
|
Schema::create('roles', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('libelle');
|
$table->string('libelle');
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public function down(): void { Schema::dropIfExists('roles'); }
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('roles');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ use Illuminate\Database\Migrations\Migration;
|
|||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
return new class extends Migration {
|
return new class extends Migration
|
||||||
public function up(): void {
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
Schema::create('users', function (Blueprint $table) {
|
Schema::create('users', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('name');
|
$table->string('name');
|
||||||
@@ -17,5 +19,9 @@ return new class extends Migration {
|
|||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public function down(): void { Schema::dropIfExists('users'); }
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('users');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ use Illuminate\Database\Migrations\Migration;
|
|||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
return new class extends Migration {
|
return new class extends Migration
|
||||||
public function up(): void {
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
Schema::create('stress_events', function (Blueprint $table) {
|
Schema::create('stress_events', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('event_name');
|
$table->string('event_name');
|
||||||
@@ -13,5 +15,9 @@ return new class extends Migration {
|
|||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public function down(): void { Schema::dropIfExists('stress_events'); }
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('stress_events');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ use Illuminate\Database\Migrations\Migration;
|
|||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
return new class extends Migration {
|
return new class extends Migration
|
||||||
public function up(): void {
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
Schema::create('relaxation_activities', function (Blueprint $table) {
|
Schema::create('relaxation_activities', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string('title');
|
$table->string('title');
|
||||||
@@ -17,5 +19,9 @@ return new class extends Migration {
|
|||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public function down(): void { Schema::dropIfExists('relaxation_activities'); }
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('relaxation_activities');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ use Illuminate\Database\Migrations\Migration;
|
|||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
return new class extends Migration {
|
return new class extends Migration
|
||||||
public function up(): void {
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
Schema::create('resultat_diags', function (Blueprint $table) {
|
Schema::create('resultat_diags', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('id_user')->constrained('users')->onDelete('cascade');
|
$table->foreignId('id_user')->constrained('users')->onDelete('cascade');
|
||||||
@@ -16,5 +18,9 @@ return new class extends Migration {
|
|||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public function down(): void { Schema::dropIfExists('resultat_diags'); }
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('resultat_diags');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ use Illuminate\Database\Migrations\Migration;
|
|||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
return new class extends Migration {
|
return new class extends Migration
|
||||||
public function up(): void {
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
Schema::create('emotion_records', function (Blueprint $table) {
|
Schema::create('emotion_records', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
|
||||||
@@ -15,5 +17,9 @@ return new class extends Migration {
|
|||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public function down(): void { Schema::dropIfExists('emotion_records'); }
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('emotion_records');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('personal_access_tokens', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->morphs('tokenable');
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('token', 64)->unique();
|
||||||
|
$table->text('abilities')->nullable();
|
||||||
|
$table->timestamp('last_used_at')->nullable();
|
||||||
|
$table->timestamp('expires_at')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('personal_access_tokens');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('cache', function (Blueprint $table) {
|
||||||
|
$table->string('key')->primary();
|
||||||
|
$table->mediumText('value');
|
||||||
|
$table->bigInteger('expiration')->index();
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('cache_locks', function (Blueprint $table) {
|
||||||
|
$table->string('key')->primary();
|
||||||
|
$table->string('owner');
|
||||||
|
$table->bigInteger('expiration')->index();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('cache');
|
||||||
|
Schema::dropIfExists('cache_locks');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('sessions', function (Blueprint $table) {
|
||||||
|
$table->string('id')->primary();
|
||||||
|
$table->foreignId('user_id')->nullable()->index();
|
||||||
|
$table->string('ip_address', 45)->nullable();
|
||||||
|
$table->text('user_agent')->nullable();
|
||||||
|
$table->longText('payload');
|
||||||
|
$table->integer('last_activity')->index();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('sessions');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
//
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
//
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_active')->default(true)->after('id_role');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_active');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('information', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('title');
|
||||||
|
$table->text('content');
|
||||||
|
$table->string('category')->default('Général'); // ex: Prévention, Stress, Sommeil
|
||||||
|
$table->string('image_url')->nullable();
|
||||||
|
$table->boolean('is_published')->default(true);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('information');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table de liaison entre un utilisateur CESIZen et le ticket (issue) qu'il a
|
||||||
|
* ouvert dans Gitea. Elle permet de retrouver « ses » tickets alors que
|
||||||
|
* l'application les crée toutes sous un même compte de service.
|
||||||
|
*/
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('tickets', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||||
|
$table->unsignedInteger('gitea_number'); // numéro de l'issue Gitea
|
||||||
|
$table->string('title');
|
||||||
|
$table->string('type');
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->index(['user_id', 'gitea_number']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('tickets');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
use App\Models\User;
|
|
||||||
use App\Models\Role;
|
use App\Models\Role;
|
||||||
|
use App\Models\User;
|
||||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
@@ -26,11 +26,13 @@ class DatabaseSeeder extends Seeder
|
|||||||
|
|
||||||
$adminRole = Role::where('libelle', 'Admin')->first();
|
$adminRole = Role::where('libelle', 'Admin')->first();
|
||||||
|
|
||||||
User::create([
|
User::firstOrCreate(
|
||||||
|
['email' => 'admin@cesizen.fr'],
|
||||||
|
[
|
||||||
'name' => 'AdminCESI',
|
'name' => 'AdminCESI',
|
||||||
'email' => 'admin@cesizen.fr',
|
'password' => Hash::make('okokokok'),
|
||||||
'password' => Hash::make('okokokok'), // Toujours hacher !
|
|
||||||
'id_role' => $adminRole->id,
|
'id_role' => $adminRole->id,
|
||||||
]);
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class RelaxationActivitySeeder extends Seeder
|
|||||||
'type' => 'audio',
|
'type' => 'audio',
|
||||||
'category' => 'Méditation',
|
'category' => 'Méditation',
|
||||||
'url' => 'https://www.youtube.com/watch?v=sz7cpV7ERsY',
|
'url' => 'https://www.youtube.com/watch?v=sz7cpV7ERsY',
|
||||||
'image_url' => 'https://images.unsplash.com/photo-1506126613408-eca07ce68773?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80'
|
'image_url' => 'https://images.unsplash.com/photo-1506126613408-eca07ce68773?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'title' => 'Yoga Doux pour le Soir',
|
'title' => 'Yoga Doux pour le Soir',
|
||||||
@@ -24,7 +24,7 @@ class RelaxationActivitySeeder extends Seeder
|
|||||||
'type' => 'video',
|
'type' => 'video',
|
||||||
'category' => 'Sport',
|
'category' => 'Sport',
|
||||||
'url' => 'https://www.youtube.com/watch?v=v7AYKMP6rOE',
|
'url' => 'https://www.youtube.com/watch?v=v7AYKMP6rOE',
|
||||||
'image_url' => 'https://images.unsplash.com/photo-1544367567-0f2fcb009e0b?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80'
|
'image_url' => 'https://images.unsplash.com/photo-1544367567-0f2fcb009e0b?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'title' => 'Bruits de Plage et Vagues',
|
'title' => 'Bruits de Plage et Vagues',
|
||||||
@@ -32,7 +32,7 @@ class RelaxationActivitySeeder extends Seeder
|
|||||||
'type' => 'audio',
|
'type' => 'audio',
|
||||||
'category' => 'Musique',
|
'category' => 'Musique',
|
||||||
'url' => 'https://www.youtube.com/watch?v=0_fS_pS_B0U',
|
'url' => 'https://www.youtube.com/watch?v=0_fS_pS_B0U',
|
||||||
'image_url' => 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80'
|
'image_url' => 'https://images.unsplash.com/photo-1507525428034-b723cf961d3e?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'title' => 'Comprendre le Cycle du Stress',
|
'title' => 'Comprendre le Cycle du Stress',
|
||||||
@@ -40,7 +40,7 @@ class RelaxationActivitySeeder extends Seeder
|
|||||||
'type' => 'article',
|
'type' => 'article',
|
||||||
'category' => 'Lecture',
|
'category' => 'Lecture',
|
||||||
'url' => 'https://www.santepubliquefrance.fr',
|
'url' => 'https://www.santepubliquefrance.fr',
|
||||||
'image_url' => 'https://images.unsplash.com/photo-1512820790803-83ca734da794?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80'
|
'image_url' => 'https://images.unsplash.com/photo-1512820790803-83ca734da794?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'title' => 'Musique Classique pour la Concentration',
|
'title' => 'Musique Classique pour la Concentration',
|
||||||
@@ -48,7 +48,7 @@ class RelaxationActivitySeeder extends Seeder
|
|||||||
'type' => 'audio',
|
'type' => 'audio',
|
||||||
'category' => 'Musique',
|
'category' => 'Musique',
|
||||||
'url' => 'https://www.youtube.com/watch?v=5Qq3m9G4_4A',
|
'url' => 'https://www.youtube.com/watch?v=5Qq3m9G4_4A',
|
||||||
'image_url' => 'https://images.unsplash.com/photo-1514119412350-e174d90d280e?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80'
|
'image_url' => 'https://images.unsplash.com/photo-1514119412350-e174d90d280e?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'title' => 'Techniques de Respiration Wim Hof',
|
'title' => 'Techniques de Respiration Wim Hof',
|
||||||
@@ -56,8 +56,8 @@ class RelaxationActivitySeeder extends Seeder
|
|||||||
'type' => 'video',
|
'type' => 'video',
|
||||||
'category' => 'Respiration',
|
'category' => 'Respiration',
|
||||||
'url' => 'https://www.youtube.com/watch?v=tybOi4hjZFQ',
|
'url' => 'https://www.youtube.com/watch?v=tybOi4hjZFQ',
|
||||||
'image_url' => 'https://images.unsplash.com/photo-1518199266791-5375a83190b7?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80'
|
'image_url' => 'https://images.unsplash.com/photo-1518199266791-5375a83190b7?ixlib=rb-4.0.3&auto=format&fit=crop&w=800&q=80',
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach ($activities as $activity) {
|
foreach ($activities as $activity) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
use App\Models\Role;
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
class RoleSeeder extends Seeder
|
class RoleSeeder extends Seeder
|
||||||
@@ -18,7 +18,7 @@ class RoleSeeder extends Seeder
|
|||||||
];
|
];
|
||||||
|
|
||||||
foreach ($roles as $role) {
|
foreach ($roles as $role) {
|
||||||
\App\Models\Role::create($role);
|
Role::create($role);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
use App\Models\StressEvent;
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
class StressEventSeeder extends Seeder
|
class StressEventSeeder extends Seeder
|
||||||
@@ -56,7 +56,7 @@ class StressEventSeeder extends Seeder
|
|||||||
];
|
];
|
||||||
|
|
||||||
foreach ($events as $event) {
|
foreach ($events as $event) {
|
||||||
\App\Models\StressEvent::create($event);
|
StressEvent::create($event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Serveur SonarQube (auto-hébergé) pour l'analyse de qualité du code.
|
||||||
|
#
|
||||||
|
# Démarrage : docker compose -f docker-compose.sonar.yml up -d
|
||||||
|
# Interface : http://localhost:9000 (identifiants par défaut : admin / admin)
|
||||||
|
# Arrêt : docker compose -f docker-compose.sonar.yml down
|
||||||
|
#
|
||||||
|
# ⚠️ Prérequis hôte (Elasticsearch embarqué dans SonarQube) :
|
||||||
|
# Linux : sudo sysctl -w vm.max_map_count=262144
|
||||||
|
# (à rendre permanent dans /etc/sysctl.conf)
|
||||||
|
# Prévoir ~2-3 Go de RAM disponibles pour le conteneur.
|
||||||
|
|
||||||
|
services:
|
||||||
|
sonarqube:
|
||||||
|
image: sonarqube:community
|
||||||
|
container_name: sonarqube
|
||||||
|
depends_on:
|
||||||
|
- sonar-db
|
||||||
|
ports:
|
||||||
|
- "9000:9000"
|
||||||
|
environment:
|
||||||
|
SONAR_JDBC_URL: jdbc:postgresql://sonar-db:5432/sonar
|
||||||
|
SONAR_JDBC_USERNAME: sonar
|
||||||
|
SONAR_JDBC_PASSWORD: sonar
|
||||||
|
volumes:
|
||||||
|
- sonarqube_data:/opt/sonarqube/data
|
||||||
|
- sonarqube_extensions:/opt/sonarqube/extensions
|
||||||
|
- sonarqube_logs:/opt/sonarqube/logs
|
||||||
|
|
||||||
|
sonar-db:
|
||||||
|
image: postgres:15
|
||||||
|
container_name: sonar-db
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: sonar
|
||||||
|
POSTGRES_PASSWORD: sonar
|
||||||
|
POSTGRES_DB: sonar
|
||||||
|
volumes:
|
||||||
|
- sonar_postgresql:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
sonarqube_data:
|
||||||
|
sonarqube_extensions:
|
||||||
|
sonarqube_logs:
|
||||||
|
sonar_postgresql:
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
services:
|
||||||
|
# PHP Application
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: cesizen-app
|
||||||
|
restart: unless-stopped
|
||||||
|
tty: true
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
|
volumes:
|
||||||
|
- ./:/var/www
|
||||||
|
- /var/www/node_modules
|
||||||
|
- ./storage:/var/www/storage
|
||||||
|
networks:
|
||||||
|
- cesizen-network
|
||||||
|
|
||||||
|
# Nginx Service
|
||||||
|
webserver:
|
||||||
|
image: nginx:alpine
|
||||||
|
container_name: cesizen-webserver
|
||||||
|
restart: unless-stopped
|
||||||
|
tty: true
|
||||||
|
ports:
|
||||||
|
- "8090:80" # WEB
|
||||||
|
- "8091:81" # API
|
||||||
|
volumes:
|
||||||
|
- ./:/var/www
|
||||||
|
- ./nginx.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
networks:
|
||||||
|
- cesizen-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
cesizen-network:
|
||||||
|
driver: bridge
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Configuration GrumPHP : vérifications automatiques de qualité du code.
|
||||||
|
# Lancement manuel : vendor/bin/grumphp run
|
||||||
|
#
|
||||||
|
# NB : dans ce dépôt (monorepo), l'installation automatique du hook Git est
|
||||||
|
# désactivée (extra.grumphp.disable-plugin dans composer.json) pour ne pas
|
||||||
|
# interférer avec le dépôt racine. Les mêmes contrôles sont exécutés en CI.
|
||||||
|
grumphp:
|
||||||
|
# On laisse le temps aux analyses (Larastan peut être un peu long).
|
||||||
|
process_timeout: 120
|
||||||
|
|
||||||
|
# On n'arrête pas au premier échec : on veut voir TOUS les problèmes
|
||||||
|
# (Pint ET PHPStan) en une seule fois.
|
||||||
|
stop_on_failure: false
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
# 1) Style de code (Laravel Pint) — mêmes règles qu'en CI.
|
||||||
|
shell:
|
||||||
|
scripts:
|
||||||
|
- ['-c', 'vendor/bin/pint --test']
|
||||||
|
|
||||||
|
# 2) Analyse statique (Larastan / PHPStan) — détecte les vrais bugs.
|
||||||
|
phpstan:
|
||||||
|
configuration: phpstan.neon
|
||||||
|
# false => on analyse les chemins définis dans phpstan.neon (app/)
|
||||||
|
# plutôt que seulement les fichiers modifiés : analyse plus fiable.
|
||||||
|
use_grumphp_paths: false
|
||||||
|
# Larastan charge tout le framework : 128M par défaut ne suffit pas.
|
||||||
|
memory_limit: '512M'
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Configuration pour le Web (Port 80 interne, mappé sur 8090)
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _; # Accepte tout car NPM a déjà filtré le domaine
|
||||||
|
index index.php index.html;
|
||||||
|
root /var/www/public;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.php?$query_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ \.php$ {
|
||||||
|
try_files $uri =404;
|
||||||
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||||
|
fastcgi_pass app:9000;
|
||||||
|
fastcgi_index index.php;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||||
|
|
||||||
|
# Optimisation pour le proxy
|
||||||
|
fastcgi_param HTTP_X_FORWARDED_FOR $http_x_forwarded_for;
|
||||||
|
fastcgi_param HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Configuration pour l'API (Port 81 interne, mappé sur 8091)
|
||||||
|
server {
|
||||||
|
listen 81;
|
||||||
|
server_name _; # Accepte tout car NPM a déjà filtré le domaine
|
||||||
|
index index.php index.html;
|
||||||
|
root /var/www/public;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.php?$query_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ \.php$ {
|
||||||
|
try_files $uri =404;
|
||||||
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||||
|
fastcgi_pass app:9000;
|
||||||
|
fastcgi_index index.php;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||||
|
|
||||||
|
# Optimisation pour le proxy
|
||||||
|
fastcgi_param HTTP_X_FORWARDED_FOR $http_x_forwarded_for;
|
||||||
|
fastcgi_param HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user