Compare commits

..

9 Commits

Author SHA1 Message Date
gitea-actions 0de63d3136 chore : bump version to v1.9.52
Auto Tag Develop / tag (push) Successful in 12s
Build & Push Docker Image / build (push) Successful in 1m42s
2026-06-29 14:40:33 +00:00
Matthieu 35f3feb59c fix(security) : passe le MCP inventory en HTTPS dans .mcp.json
Auto Tag Develop / tag (push) Successful in 9s
Le endpoint était en http:// : les credentials statiques (X-Profile-Id /
X-Profile-Password) transitaient en clair sur le premier hop avant la
redirection 301 vers https. Le serveur sert déjà le TLS (CA MALIO embarquée
côté image prod), donc on bascule l'URL en https://.
2026-06-29 16:40:23 +02:00
gitea-actions e0ac8e75be chore : bump version to v1.9.51
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Failing after 19s
2026-06-29 14:38:52 +00:00
Matthieu 7cad0f933f fix(infra) : embarque la CA racine MALIO dans l'image prod
Auto Tag Develop / tag (push) Successful in 13s
Le SDK Sentry (back) valide le TLS de GlitchTip (logs.malio-dev.fr, certificat
signé par la CA interne MALIO auto-signée). Sans cette CA dans le trust store du
conteneur, le handshake échoue et l'event est silencieusement abandonné
(sentry:test -> "Message not sent"). On installe ca-certificates, on copie la
root CA et on lance update-ca-certificates, comme côté Lesstime.
2026-06-29 16:38:38 +02:00
gitea-actions b8272eb43e chore : bump version to v1.9.50
Auto Tag Develop / tag (push) Successful in 12s
Build & Push Docker Image / build (push) Successful in 1m28s
2026-06-29 14:17:41 +00:00
matthieu add6129ee5 Merge pull request 'feat(infra) : branche le SDK Sentry (back + front) vers GlitchTip' (#7) from feat/glitchtip-error-tracking into develop
Auto Tag Develop / tag (push) Successful in 14s
Reviewed-on: #7
2026-06-29 14:17:28 +00:00
matthieu 746578c2cf chore(mcp) : retire l'override lesstime du .mcp.json (utilise la conf globale)
Le serveur MCP lesstime était redéfini en HTTP (405) ; on s'appuie désormais
sur la conf user globale (~/.claude.json, HTTPS) comme les autres projets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 13:15:23 +02:00
matthieu d3f1e95711 feat(infra) : injecte les DSN Sentry au build prod (Dockerfile + CI Gitea)
Complète le branchement Sentry/GlitchTip côté déploiement pour que le front
reçoive son DSN et uploade ses source maps en prod.

- infra/prod/Dockerfile (stage frontend-build) : ARG NUXT_PUBLIC_SENTRY_DSN +
  SENTRY_URL/ORG/PROJECT/AUTH_TOKEN, passés en préfixe inline du RUN npm run
  generate (pas en ENV → token non persisté ; stage intermédiaire jeté de toute
  façon). Vides par défaut => module Sentry inerte, pas d'upload.
- .gitea/workflows/build-docker.yml : --build-arg depuis les secrets Gitea
  (INVENTORY_SENTRY_DSN_FRONT, SENTRY_URL, SENTRY_ORG, SENTRY_PROJECT,
  SENTRY_AUTH_TOKEN).
- infra/prod/.env.example : documente SENTRY_DSN (back, runtime).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 13:02:56 +02:00
matthieu 517aefcd9b feat(infra) : branche le SDK Sentry (back + front) vers GlitchTip
Error tracking centralisé (ticket INFRA #146) : remontée des erreurs back
(Symfony) et front (Nuxt) vers l'instance GlitchTip auto-hébergée.

Backend :
- sentry/sentry-symfony ^5.10, bundle enregistré prod-only
- config/packages/sentry.yaml : handler Monolog niveau ERROR+, ignore les
  4xx/AccessDenied, pas d'APM, release = %app.version%
- services.yaml importe version.yaml pour exposer app.version au container
- .env : bloc SENTRY_DSN documenté (vide => SDK inerte)

Frontend :
- @sentry/nuxt ^10.61, module chargé uniquement si NUXT_PUBLIC_SENTRY_DSN défini
- runtimeConfig.public.sentry + source maps (hidden) + options d'upload
- sentry.client.config.ts : init côté client gardée par if (dsn)

DSN vides par défaut : aucune erreur n'est envoyée tant que les projets
GlitchTip inventory-api / inventory-front et leurs DSN ne sont pas configurés
en prod. Aucun secret commité.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 12:54:55 +02:00
19 changed files with 1424 additions and 806 deletions
+7
View File
@@ -40,3 +40,10 @@ DEFAULT_URI=http://localhost
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
###< nelmio/cors-bundle ###
###> sentry/sentry-symfony ###
# Error tracking backend → GlitchTip (projet "inventory-api"). Prod only, vide => inerte.
# À définir dans l'env de prod (PAS ici, pas de secret commité). Format :
# SENTRY_DSN=http://<clé>@<host-ou-IP>:<port>/<id-projet>
# SENTRY_DSN=
###< sentry/sentry-symfony ###
+5
View File
@@ -20,6 +20,11 @@ jobs:
run: |
docker build \
-f infra/prod/Dockerfile \
--build-arg NUXT_PUBLIC_SENTRY_DSN="${{ secrets.INVENTORY_SENTRY_DSN_FRONT }}" \
--build-arg SENTRY_URL="${{ secrets.SENTRY_URL }}" \
--build-arg SENTRY_ORG="${{ secrets.SENTRY_ORG }}" \
--build-arg SENTRY_PROJECT="${{ secrets.SENTRY_PROJECT }}" \
--build-arg SENTRY_AUTH_TOKEN="${{ secrets.SENTRY_AUTH_TOKEN }}" \
-t gitea.malio.fr/malio-dev/inventory:${{ gitea.ref_name }} \
-t gitea.malio.fr/malio-dev/inventory:latest \
.
+1 -8
View File
@@ -2,18 +2,11 @@
"mcpServers": {
"inventory": {
"type": "http",
"url": "http://inventory.malio-dev.fr/_mcp",
"url": "https://inventory.malio-dev.fr/_mcp",
"headers": {
"X-Profile-Id": "admin-default-profile",
"X-Profile-Password": "A123"
}
},
"lesstime": {
"type": "http",
"url": "http://project.malio-dev.fr/_mcp",
"headers": {
"Authorization": "Bearer b355c6cbf27d2a86d7eba1c3132c99bb3133f94cfd9e9243ffcc3c5ae1dc82c8"
}
}
}
}
-434
View File
@@ -1,434 +0,0 @@
# Review complète — Projet Inventory
> Audit éducatif du projet. Chaque point explique le problème, pourquoi c'est un problème, et comment le corriger.
> Document généré le 2026-06-11 (branche `develop`, v1.9.47).
> Complément de `docs/REVIEW_ARCHITECTURE.md` (2026-03-23) — les findings d'architecture n'y sont pas dupliqués, seul leur **statut** est mis à jour ici (§4).
---
## Table des matières
1. [Sécurité](#1-sécurité)
2. [Bugs fonctionnels](#2-bugs-fonctionnels)
3. [Code mort et violations des règles projet](#3-code-mort-et-violations-des-règles-projet)
4. [Dette d'architecture — suivi REVIEW_ARCHITECTURE.md](#4-dette-darchitecture--suivi-review_architecturemd)
5. [Documentation et configuration](#5-documentation-et-configuration)
6. [Frontend et UX](#6-frontend-et-ux)
7. [CI/CD et dépendances](#7-cicd-et-dépendances)
8. [Hygiène git](#8-hygiène-git)
9. [Bonnes pratiques à retenir](#9-bonnes-pratiques-à-retenir)
---
## 1. Sécurité
### 1.1 CRITIQUE — Credentials de production commités dans `.mcp.json`
**Fichier :** `.mcp.json` (tracké dans git, **pas** dans `.gitignore`)
Le fichier contient le mot de passe du profil admin MCP de production (`X-Profile-Password: A123` pour `inventory.malio-dev.fr`) **et** un bearer token Lesstime valide (`project.malio-dev.fr`). Toute personne ayant accès au dépôt (ou à son historique, même après suppression du fichier) peut s'authentifier sur les deux systèmes de production.
**Pourquoi c'est grave :** un secret commité reste dans l'historique git pour toujours. Le token Lesstime donne accès à tout le système de gestion de projet (tâches, temps, absences). Le mot de passe `A123` est en plus trivial.
**Correction :**
1. **Révoquer/changer** les deux secrets (mot de passe du profil MCP + régénérer le token Lesstime) — c'est l'étape la plus importante, supprimer le fichier ne suffit pas.
2. `git rm --cached .mcp.json` + ajouter `.mcp.json` au `.gitignore`.
3. Conserver un `.mcp.json.example` avec des placeholders.
### 1.2 CRITIQUE — `create_test_user.php` tracké à la racine avec credentials admin en clair
**Fichier :** `create_test_user.php:16,59-61` (tracké dans git)
Script de debug qui crée un compte `ROLE_ADMIN` avec `admin@admin.com` / `admin123` (hardcodé et affiché en clair sur stdout), connexion PDO brute `root`/`root`. Posé à la racine du projet, il est embarquable dans une image de prod et exécutable partout où `vendor/` existe.
**Pourquoi c'est grave :** si ce script tourne (ou a tourné) en production, il existe un compte admin avec un mot de passe devinable en 3 essais. Les credentials sont aussi dans l'historique git.
**Correction :** supprimer le fichier du dépôt (`git rm`), vérifier **en prod** qu'aucun profil `admin@admin.com` actif n'existe. Le besoin légitime (seed d'un admin de dev) est déjà couvert par `fixtures/` ou peut devenir une commande Symfony `app:create-admin` qui demande le mot de passe en argument.
### 1.3 IMPORTANT — Mot de passe de la base de prod hardcodé dans 9 scripts trackés
**Fichiers :** `scripts/check-prod-values.php`, `scripts/fix-prod-all.php`, `scripts/restore-custom-field-values.php`, `scripts/migrate-orphaned-custom-fields.php`, `scripts/check-prod-audit-dates.php`, `scripts/check-prod-missing-piece-cfs.php`, `scripts/check-prod-orphaned-detail.php`, `scripts/fix-prod-recreate-and-migrate.php`, `scripts/verify-prod-health.php`
Neuf scripts de réparation one-shot contiennent le couple `ferme_user`/`fermerecette` en dur — des credentials de base de données de production, dans le dépôt.
**Pourquoi c'est grave :** même problème que 1.1 — secret en clair dans l'historique. En plus ces scripts contournent Doctrine et l'audit : les exécuter par erreur modifie la prod sans trace.
**Correction :** changer le mot de passe PG concerné, puis archiver/supprimer ces scripts (ils ont déjà servi). S'ils doivent rester, lire les credentials depuis l'environnement (`getenv('DATABASE_URL')`) et les déplacer dans `_archives/` (déjà gitignoré).
### 1.4 IMPORTANT — Aucune limite de taille d'upload (ni applicative, ni infra)
**Fichiers :** `src/State/DocumentUploadProcessor.php:55-116`, `src/Controller/CommentController.php:104-137`, `infra/dev/php.ini`, `infra/prod/nginx.conf`
Aucun des deux chemins d'upload ne vérifie `$file->getSize()`. Côté infra : `php.ini` ne définit ni `upload_max_filesize` ni `post_max_size` (défauts PHP : 2 Mo / 8 Mo), et `nginx.conf` prod n'a pas de `client_max_body_size` (défaut nginx : **1 Mo**).
**Pourquoi c'est un problème (double) :**
- *Fonctionnel* : en prod, tout upload > 1 Mo est probablement rejeté par nginx avec une erreur 413 brute (non gérée par le front) — alors que l'app est censée stocker des PDF techniques.
- *Sécurité* : aucune limite **choisie** n'existe ; le jour où quelqu'un monte les limites infra "pour faire passer un gros PDF", plus rien ne protège le disque (un `ROLE_VIEWER` peut uploader via les commentaires, cf. 1.9).
**Correction :** décider d'une limite métier (ex. 50 Mo), puis l'appliquer aux 3 niveaux :
1. Check applicatif `if ($file->getSize() > self::MAX_UPLOAD_BYTES)` dans les deux chemins (erreur 400 propre).
2. `upload_max_filesize = 50M` / `post_max_size = 55M` dans `infra/dev/php.ini` **et** l'image prod.
3. `client_max_body_size 55m;` dans `infra/prod/nginx.conf`.
### 1.5 MOYEN — Garde anti path-traversal incomplet dans `DocumentStorageService`
**Fichier :** `src/Service/DocumentStorageService.php:28-42`
`getAbsolutePath()` vérifie `str_contains($relativePath, '..')` puis compare `realpath()` au répertoire de stockage — mais `realpath()` renvoie `false` pour un fichier inexistant, donc le second contrôle est **sauté** dans ce cas. Un chemin absolu (`/etc/passwd`) passe le premier contrôle (pas de `..`) : `$this->storageDir.'/'.'/etc/passwd'`… ne résout pas vers `/etc/passwd`, mais un chemin via symlink dans le storage le pourrait. L'exploitation exige d'écrire `document.path` en base (pas d'input direct utilisateur), donc le risque actuel est faible — c'est du **hardening**.
**Correction :** valider sur le répertoire parent, qui existe toujours :
```php
$absolutePath = $this->storageDir.'/'.$relativePath;
$realParent = realpath(dirname($absolutePath));
if (false === $realParent || !str_starts_with($realParent.'/', realpath($this->storageDir).'/')) {
throw new RuntimeException(sprintf('Path traversal detected: "%s"', $relativePath));
}
```
### 1.6 MOYEN — Pas de protection CSRF : `SameSite=Lax` est la seule barrière
**Fichiers :** `config/packages/framework.yaml:8`, `config/packages/nelmio_cors.yaml`
L'auth est par cookie de session, et aucun endpoint d'écriture ne vérifie de token CSRF ni de header custom. La protection repose à 100 % sur `cookie_samesite: lax` (et le CORS pour les lectures cross-origin). C'est la posture courante pour une SPA en 2026, mais c'est une **défense à un seul étage** : un navigateur ancien/exotique ou une config future en sous-domaine partagé la ferait tomber. À noter : `csrfToken` existe dans `nuxt.config.ts:59` mais n'est branché nulle part (config morte, cf. 3.1).
**Correction (peu coûteuse) :** exiger un header `X-Requested-With: XMLHttpRequest` sur les méthodes non-GET du firewall `api` (un listener de 15 lignes) — un formulaire HTML cross-site ne peut pas envoyer ce header. L'ajouter dans `useApi.ts` côté front. Supprimer le `csrfToken` mort.
### 1.7 MOYEN — `session_fixation_strategy: none` désactive la protection globalement
**Fichier :** `config/packages/security.yaml:5`, mitigé par `src/Controller/SessionProfileController.php:96`
Le choix est documenté (le `migrate` par défaut casse les requêtes concurrentes de la SPA) et le login appelle bien `$session->migrate(true)` manuellement — le flux actuel est correct. Le risque est **futur** : tout nouveau chemin d'authentification (reset de mot de passe, impersonation…) n'aura pas la régénération d'ID de session, silencieusement.
**Correction :** garder le réglage, mais l'encadrer : un test fonctionnel qui vérifie que l'ID de session change au login (échouera si quelqu'un retire le `migrate(true)`), et un commentaire dans `SessionProfileController` pointant vers `security.yaml`.
### 1.8 MOYEN — MCP HTTP : mot de passe en clair dans les headers à chaque requête
**Fichier :** `src/Mcp/Security/McpHeaderAuthenticator.php:43-44`, `infra/prod/nginx-proxy.conf`
Chaque requête MCP porte `X-Profile-Password` en clair. Les headers transitent par le proxy nginx et peuvent finir dans des logs (proxy, APM, outils de debug). Le rate-limiting et le hash côté serveur sont bien faits, mais le secret circule en permanence — et l'URL configurée dans `.mcp.json` est en **`http://`** (pas de TLS).
**Correction :** passer à un token d'API dédié (longue chaîne aléatoire, stockée hashée, comparée via le hasher existant), transmis en `Authorization: Bearer` — comme le fait déjà Lesstime. Et servir `/_mcp` uniquement en HTTPS.
### 1.9 MINEUR — Création de commentaires + upload de fichiers ouverte à `ROLE_VIEWER`
**Fichier :** `src/Controller/CommentController.php:33`
Convention du projet : lecture = `ROLE_VIEWER`, écriture = `ROLE_GESTIONNAIRE`. La création de commentaire (avec pièces jointes !) est la seule écriture accessible aux viewers. Si c'est un choix métier (« tout le monde peut commenter »), OK — mais combiné à 1.4, un compte en lecture seule peut remplir le disque.
**Correction :** confirmer le choix métier et le documenter dans `docs/BACKEND.md` ; appliquer la limite de taille de 1.4 dans tous les cas.
### 1.10 MINEUR — `download()` sans les headers de protection de `serve()`
**Fichier :** `src/Controller/DocumentServeController.php:105-116`
`serve()` envoie `X-Content-Type-Options: nosniff` + `Content-Security-Policy: sandbox` (très bien) ; `download()` n'envoie ni l'un ni l'autre pour les fichiers disque. La disposition `attachment` protège déjà beaucoup, mais l'asymétrie est gratuite.
**Correction :** copier les deux headers dans `download()`.
### 1.11 MINEUR — Pagination max très élevée sur certaines ressources
**Fichiers :** `src/Entity/Constructeur.php:47` (2000), `src/Entity/ConstructeurCategorie.php:43` (1000), `src/Entity/Document.php:55` (500)
Un `?itemsPerPage=2000` charge 2000 entités + sérialisation en une requête. Pour des catalogues internes c'est sans doute volontaire (dropdowns sans pagination), mais c'est aussi un vecteur de charge facile.
**Correction :** vérifier ce que le front demande réellement et redescendre au besoin réel (ou documenter pourquoi 2000).
### 1.12 MINEUR — `infra/dev/.env.docker.local` tracké malgré le `.gitignore`
**Fichier :** `infra/dev/.env.docker.local:27,34`
Le fichier est dans `.gitignore` (ligne 23) **mais déjà tracké** (ajouté avant la règle — gitignore n'agit pas sur les fichiers déjà suivis). Secrets de dev faibles (`changeme_…`) : sans gravité en soi, mais le fichier est censé être local et chaque dev qui le modifie crée du diff.
**Correction :** `git rm --cached infra/dev/.env.docker.local`, fournir `infra/dev/.env.docker.local.example` (référencé par le README au passage).
---
## 2. Bugs fonctionnels
### 2.1 IMPORTANT — Fallback `http://localhost:3000` dans `useApi.ts`
**Fichier :** `frontend/app/composables/useApi.ts:18`
```ts
const API_BASE_URL = (publicConfig.apiBaseUrl as string) || 'http://localhost:3000'
```
Si `NUXT_PUBLIC_API_BASE_URL` est vide au build/runtime de prod, **tous** les appels API partent silencieusement vers `localhost:3000` (le port du dev server, qui ne sert même pas l'API). Échec garanti mais difficile à diagnostiquer.
**Correction :** fallback relatif `'/api'`… attention, `useApi` préfixe déjà `/api` lui-même — le bon fallback est donc `''` (origine courante) :
```ts
const API_BASE_URL = (publicConfig.apiBaseUrl as string) || ''
```
Même nettoyage pour `nuxt.config.ts:49` (`http://localhost/api`, valeur SSR jamais utilisée puisque `ssr: false`).
### 2.2 IMPORTANT — Uploads de prod probablement plafonnés à 1 Mo par nginx
Voir 1.4 — c'est le versant fonctionnel : sans `client_max_body_size`, nginx rejette en 413 tout body > 1 Mo, et le front n'affiche pas d'erreur claire (le toast générique de `useApi` au mieux). À tester en prod avec un PDF de 5 Mo ; si les uploads passent, c'est qu'une config existe ailleurs et il faut l'aligner dans le dépôt.
### 2.3 MOYEN — Deux mécanismes de maintenance déconnectés
**Fichiers :** `src/Controller/MaintenanceController.php:56` (flag `var/maintenance`), `infra/prod/nginx.conf:6` (flag `maintenance.on`), `infra/prod/deploy.sh:47`
Le toggle admin (`PUT /api/admin/maintenance`) écrit `var/maintenance`, lu par le middleware front — maintenance **applicative**. Le déploiement crée `maintenance.on`, lu par nginx — maintenance **infra**. Les deux coexistent volontairement mais rien ne le documente : un admin qui active la maintenance via l'UI ne bloque pas les appels API directs (le flag n'est vérifié que par le middleware front), et inversement.
**Correction :** documenter les deux niveaux dans `DEPLOY.md` ; idéalement faire vérifier le flag applicatif côté backend (listener kernel.request qui renvoie 503 pour les non-admins) plutôt que de ne compter que sur le middleware front (contournable).
### 2.4 MOYEN — Provider Symfony par `email` alors que `email` est nullable
**Fichiers :** `config/packages/security.yaml:18`, `src/Entity/Profile.php:59-61`, `src/Controller/AdminProfileController.php:66`
On peut créer un profil sans email (profils « kiosque »), mais le user provider charge par `property: email`. Ça marche aujourd'hui parce que `SessionProfileAuthenticator` charge par ID — le provider n'est jamais utilisé pour ces profils. Incohérence latente : tout futur usage du provider standard (remember-me, impersonation, commande console) cassera sur ces profils.
**Correction :** soit rendre l'email obligatoire et générer un email technique pour les kiosques, soit écrire un `UserProviderInterface` custom qui charge par id **ou** email, et le déclarer dans `security.yaml`.
---
## 3. Code mort et violations des règles projet
### 3.1 MINEUR — Config morte dans `nuxt.config.ts`
**Fichier :** `frontend/nuxt.config.ts:56-59`
`csrfToken`, `requestTimeout`, `enableDebug`, `enableAnalytics`, `logLevel` : définis, jamais consommés (seul `apiTimeout` est lu par `useApi.ts`). Du code mort en config laisse croire que des fonctionnalités existent (un lecteur pense que le CSRF est géré — il ne l'est pas, cf. 1.6).
**Correction :** supprimer ces 5 clés.
### 3.2 MINEUR — Variables JWT dans un projet 100 % session
**Fichier :** `infra/dev/.env.docker.local:28-30`
`JWT_SECRET_KEY`, `JWT_PUBLIC_KEY`, `JWT_PASSPHRASE` — copiées d'un autre projet (Lesstime/Starseed utilisent JWT, pas Inventory). Le CLAUDE.md martèle « pas JWT » ; ces variables sèment le doute.
**Correction :** supprimer les 3 lignes.
### 3.3 MOYEN — `@nuxtjs/tailwindcss` : dépendance inutilisée et conflictuelle
**Fichier :** `frontend/package.json:21`
Le projet utilise Tailwind 4 via `@tailwindcss/vite` (`nuxt.config.ts:1,64`). `@nuxtjs/tailwindcss` n'est référencé nulle part et installe son propre Tailwind **v3** dans `node_modules` — bloat + risque de résolution ambiguë.
**Correction :** `npm uninstall @nuxtjs/tailwindcss` puis vérifier `npm run build`.
### 3.4 MINEUR — Dockerfile dev pollué par un template générique
**Fichier :** `infra/dev/Dockerfile:48-53,82-100`
Blocs commentés Oracle OCI8, IMAP/Kerberos, PDO MySQL/SQLite — aucun rapport avec un projet PostgreSQL. Bruit pur.
**Correction :** supprimer les blocs commentés.
### 3.5 MINEUR — `node_modules/` orphelin à la racine backend
Pas de `package.json` à la racine, mais un `node_modules/` (untracked) y traîne, et `node_modules/` n'est pas dans le `.gitignore` racine — seul le hasard l'empêche d'être commité un jour.
**Correction :** `rm -rf node_modules/` à la racine + ajouter `/node_modules/` au `.gitignore`.
### 3.6 MINEUR — 3 fichiers utils restés en `.js` non typés
**Fichiers :** `frontend/app/utils/documentPreview.js`, `frontend/app/utils/fileIcons.js`, `frontend/app/utils/printTemplates/machineReport.js`
Importés depuis du `.ts` sans aucune sécurité de type — incohérent avec la règle « TypeScript, 0 erreur typecheck ».
**Correction :** renommer en `.ts` et typer les signatures (mécanique, bon candidat Codex).
---
## 4. Dette d'architecture — suivi REVIEW_ARCHITECTURE.md
État des 10 chantiers identifiés le 2026-03-23, vérifié ce jour : **1 corrigé sur 10**, et le God controller a grossi.
| # | Source de complexité | Statut 2026-06-11 |
|---|---------------------|-------------------|
| 1 | `smartMatch` dupliqué dans les Sync Strategies | ❌ Toujours dupliqué (`ComposantSyncStrategy.php:380`, `PieceSyncStrategy.php:244`) |
| 2 | Custom Fields : 4-6 FK nullable (polymorphisme pauvre) | ❌ Inchangé, pas de contrainte CHECK |
| 3 | Composables géants | ⚠️ Partiel : `useComponentEdit.ts` 539 LOC, `usePieceEdit.ts` 404, `useComponentCreate.ts` 366 |
| 4 | Triple duplication utils custom fields | ✅ **Corrigé** — fusionné dans `shared/utils/customFields.ts` |
| 5 | `pendingStructure` canal caché | ❌ Toujours sans `try/finally` (`ModelTypeProcessor.php`) |
| 6 | `PieceProductSyncSubscriber` legacy | ❌ Inchangé (`recomputeSingleEntityChangeSet` toujours là) |
| 7 | Double flush dans les processors | ❌ Inchangé (`ComposantProcessor.php:45,132`) |
| 8 | `MachineStructureController` God controller | ❌ **Aggravé** : 300+ → **1121 lignes** |
| 9 | Dépendance circulaire `useMachineDetailData` | ❌ Proxy ref toujours en place (`:133`) |
| 10 | Typage `any` systématique | ❌ **179 occurrences** dans 26 composables |
**Le point clé :** la Phase 1 « quick wins » du plan (items 1, 5, 6, 7 — effort S chacun, sans impact d'interface) n'a pas été entamée en presque 3 mois, alors que ce sont les corrections au meilleur ratio risque/bénéfice du projet. Le `MachineStructureController` qui grossit de +150 lignes confirme la trajectoire : sans extraction de services, chaque feature l'alourdit.
---
## 5. Documentation et configuration
### 5.1 IMPORTANT — Toute la doc décrit encore un submodule qui n'existe plus
**Fichiers :** `README.md:49-50,291-296`, `DEPLOY.md:58,62,208`, `RELEASE.md:49,117`, `frontend/README.md:150-154`
Le frontend a été intégré au monorepo (cf. CLAUDE.md « plus de submodule »), mais le README explique `git clone --recurse-submodules`, un workflow de commit en deux temps, et DEPLOY/RELEASE font des `git submodule update`. Un nouveau dev qui suit le README perd du temps sur des commandes sans effet ; un déploiement scripté depuis DEPLOY.md exécute des étapes mortes.
**Correction :** purger toute mention de submodule des 4 fichiers, décrire le workflow monorepo (1 commit racine).
### 5.2 IMPORTANT — `DEPLOY.md` utilise l'utilisateur PG d'un autre projet
**Fichier :** `DEPLOY.md` (6 occurrences de `ferme_user`)
Les commandes psql/backup de DEPLOY.md utilisent `ferme_user`/`fermerecette` — copié-collé du projet **Ferme**. Le vrai user prod est `inventory_user` (cf. `infra/prod/.env.example`). Quelqu'un qui suit la doc en incident de prod tape des commandes qui échouent (ou pire, sur la mauvaise base si les deux co-habitent sur l'instance partagée).
**Correction :** remplacer les 6 occurrences par `inventory_user` et des placeholders de mot de passe.
### 5.3 MOYEN — `RELEASE.md` et `CLAUDE.md` référencent un fichier `VERSION` inexistant
**Fichiers :** `RELEASE.md:17,50,80,82`, `CLAUDE.md` (arbre projet)
La version vit dans `config/version.yaml` (1.9.47) — le fichier `VERSION` n'existe plus. `scripts/release.sh` et la CI (`auto-tag-develop.yml`) sont à jour, mais la doc décrit l'ancien système, y compris « `frontend/nuxt.config.ts` lit `VERSION` au build ».
**Correction :** mettre à jour RELEASE.md et la ligne d'arbre dans CLAUDE.md vers `config/version.yaml`.
### 5.4 MINEUR — Pas de `.env.example` backend ni de `.env.docker.local.example`
Un nouveau dev n'a aucun modèle des variables attendues côté backend (le README pointe vers `infra/dev/.env.docker.local`… qui est censé être local/ignoré, cf. 1.12).
**Correction :** créer `infra/dev/.env.docker.local.example` avec placeholders.
### 5.5 MINEUR — Fichiers de données métier à la racine du projet
`Company (1).json`, `customer.json`, `customer.original.json`, `Ensemble simple rotor.pdf`, `inventory_prod (2).sql.gz` traînent à la racine (untracked — le `.sql.gz` est protégé par le gitignore, **pas les `.json` ni le `.pdf`** : un `git add .` distrait les commiterait, et `customer.json` ressemble à des données client réelles → RGPD).
**Correction :** déplacer hors du dépôt (ex. `~/imports/`), et ajouter une règle défensive au `.gitignore` (`/*.json` racine, `/*.pdf`).
---
## 6. Frontend et UX
### 6.1 MOYEN — Erreurs avalées : `console.error` sans feedback utilisateur
**Fichiers :** 57+ occurrences ; ex. `useMachineDetailData.ts:372,385`, `useProfileSession.ts:27`
Le pattern dominant en cas d'échec API est `console.error(...)` puis on continue avec `null`/`[]`. `useApi` toaste déjà les erreurs HTTP, donc une partie est couverte — mais les erreurs réseau/parsing et les branches qui catchent **avant** le toast laissent l'utilisateur devant une page partiellement vide sans explication, et créent du double-reporting ailleurs.
**Correction :** définir une règle unique : `useApi` est le seul à toaster ; les composables ne re-loggent pas, mais positionnent un état d'erreur (`error.value = ...`) que la page affiche (bandeau « Impossible de charger X — Réessayer »).
### 6.2 MOYEN — Appel `/maintenance/check` à chaque navigation
**Fichier :** `frontend/app/middleware/profile.global.ts:34-39`
Chaque changement de route d'un non-admin déclenche un aller-retour API. Latence ajoutée à **toutes** les navigations pour un état qui change une fois par an.
**Correction :** cacher le résultat dans un `useState` avec TTL (ex. 60 s) ; le 503 éventuel d'un appel API normal peut aussi servir de signal.
### 6.3 Rappels (déjà au §4)
`any` ×179, composables géants, dépendance circulaire — voir tableau §4 et `docs/REVIEW_ARCHITECTURE.md` pour les solutions détaillées.
---
## 7. CI/CD et dépendances
### 7.1 IMPORTANT — Aucun garde-fou automatisé : la CI ne lance ni tests ni lint
**Fichiers :** `.gitea/workflows/auto-tag-develop.yml`, `.gitea/workflows/build-docker.yml`, workflow de commit (CLAUDE.md : « committer avec `--no-verify` »)
La chaîne actuelle : push sur `develop` → auto-tag → build Docker → image prod. **Aucune étape ne lance PHPUnit, php-cs-fixer, ESLint ou `nuxi typecheck`.** Et comme le hook pre-commit (qui devait jouer ce rôle) est trop lent, la convention projet est de le contourner avec `--no-verify`. Résultat : il est possible de tagger et construire une image de prod avec des tests rouges sans qu'aucun système ne le signale.
**Pourquoi c'est le finding process le plus important de cette review :** la suite de tests est bonne (48 fichiers, DAMA, factories) — mais une suite de tests qui ne tourne pas en CI ne protège rien.
**Correction :** ajouter un workflow `ci.yml` déclenché sur PR + push develop :
1. `composer install` + `php-cs-fixer --dry-run` + PHPUnit (avec un service PG 16).
2. `npm ci` + `eslint` + `npx nuxi typecheck` + `npm run build` dans `frontend/`.
3. Faire dépendre `auto-tag-develop` du succès de la CI (ou au minimum bloquer les PR).
Le hook pre-commit lent peut alors être assumé comme optionnel.
### 7.2 MOYEN — Le build Docker de prod n'est pas testé avant le push
`build-docker.yml` build + push `latest` dès qu'un tag est posé — sans health-check de l'image (la CI de 7.1 règle l'essentiel ; un `docker run --rm image php bin/console about` est un bon smoke test bon marché).
### 7.3 Dépendances
- Backend : propre, à jour (Symfony 8.0.*, AP ^4.2, ORM ^3.6). `symfony/twig-bundle` à vérifier : aucun template Twig dans `templates/` — si seul le MCP bundle le requiert, le laisser en dépendance transitive.
- Frontend : `@nuxtjs/tailwindcss` à supprimer (cf. 3.3). Le reste est à jour et utilisé.
---
## 8. Hygiène git
Synthèse des fichiers trackés à tort (détails en §1/§5) :
| Fichier | Problème | Action |
|---------|----------|--------|
| `.mcp.json` | Credentials prod | `git rm --cached` + gitignore + **rotation** |
| `create_test_user.php` | Script debug + credentials | `git rm` |
| `scripts/{check,fix,restore,migrate,verify}-prod-*.php` (9) | Mot de passe PG prod | rotation + archive/suppression |
| `infra/dev/.env.docker.local` | Censé être local, déjà tracké | `git rm --cached` + `.example` |
| `.claude/settings.json` | Inoffensif (config plugins) | OK, peut rester |
Gaps `.gitignore` racine : `/node_modules/`, `.mcp.json`, `/*.json` (défensif), `/*.pdf`.
> Note : contrairement au premier rapport d'agent, `.claude/settings.local.json` n'est **pas** tracké — pas d'action nécessaire.
---
## 9. Bonnes pratiques à retenir
### Ce qui est bien fait dans le projet
- **`declare(strict_types=1)` partout**, attributs PHP 8 modernes, code backend homogène et lisible.
- **Sécurité API systématique** : chaque opération API Platform porte son `security:`, chaque controller custom ouvre par `denyAccessUnlessGranted()` — aucun endpoint accidentellement public trouvé.
- **Rate limiting** sur le login **et** sur l'auth MCP (souvent oublié ailleurs).
- **IDs CUID** : pas d'énumération séquentielle possible.
- **Upload : validation MIME par contenu** (`finfo`), nom de fichier régénéré (CUID), stockage hors de `public/` — et SVG exclu de l'allowlist (XSS évité).
- **`serve()` documents** : `nosniff` + `CSP: sandbox` — au-dessus du standard.
- **Système d'audit** propre (`AbstractAuditSubscriber` en template method, flag `skipAudit` réfléchi).
- **Tests solides** : 48 fichiers, `AbstractApiTestCase` avec factories, DAMA rollback, coûts de hash réduits en test.
- **Le refacto `customFields.ts` a été fait** — la dette de REVIEW_ARCHITECTURE n'est pas ignorée, juste lente.
- **Docker prod multi-stage** propre, page maintenance nginx, volumes nommés pour le storage.
- **Zéro `console.log`**, zéro TODO/FIXME oublié dans `src/` et `frontend/app/`.
### Les règles à graver
1. **Un secret commité est un secret grillé** : la suppression du fichier ne suffit jamais, il faut **révoquer** (cf. `.mcp.json`, scripts prod).
2. **`.gitignore` n'agit pas sur les fichiers déjà trackés** — `git rm --cached` d'abord.
3. **Une suite de tests qui ne tourne pas en CI ne protège rien** : si le hook est trop lent pour être vécu, le garde-fou doit vivre en CI.
4. **Toute limite (taille, pagination, timeout) doit être choisie, pas héritée d'un défaut** — sinon c'est le défaut le plus bas de la chaîne qui décide (nginx 1 Mo).
5. **Un fallback doit échouer bruyamment ou être correct**`|| 'http://localhost:3000'` en prod est le pire des deux mondes.
6. **La doc copiée d'un autre projet est pire que pas de doc** (`ferme_user` dans DEPLOY.md).
7. **Config morte = mensonge** : un `csrfToken` non branché fait croire qu'une protection existe.
8. **Les scripts one-shot ont une date de péremption** : après usage → `_archives/` ou suppression.
9. **Quand un God controller existe, chaque feature le fait grossir** : extraire tôt (1121 lignes et ça monte).
10. **Les quick wins planifiés perdent leur valeur s'ils ne sont jamais faits** : la Phase 1 de REVIEW_ARCHITECTURE (4×effort S) attend depuis 3 mois.
---
## Résumé par priorité
| Priorité | # | Problème | Fichier |
|----------|---|----------|---------|
| **P0** | 1.1 | Credentials prod + token Lesstime commités (rotation requise) | `.mcp.json` |
| **P0** | 1.2 | Script admin avec mot de passe en clair tracké | `create_test_user.php` |
| **P0** | 1.3 | Mot de passe PG prod dans 9 scripts trackés | `scripts/*-prod-*.php` |
| **P1** | 7.1 | CI sans tests ni lint + hook contourné | `.gitea/workflows/` |
| **P1** | 1.4/2.2 | Aucune limite d'upload choisie (et défaut nginx 1 Mo) | `DocumentUploadProcessor.php`, `CommentController.php`, `infra/` |
| **P1** | 2.1 | Fallback API `localhost:3000` en prod | `frontend/app/composables/useApi.ts:18` |
| **P1** | 5.1 | Doc submodule obsolète (4 fichiers) | `README.md`, `DEPLOY.md`, `RELEASE.md`, `frontend/README.md` |
| **P1** | 5.2 | `ferme_user` dans DEPLOY.md | `DEPLOY.md` |
| **P1** | 1.5 | Garde path-traversal incomplet | `DocumentStorageService.php:28-42` |
| **P2** | 1.6 | CSRF : une seule barrière (SameSite) | `framework.yaml`, `useApi.ts` |
| **P2** | 1.7 | Session fixation : protection désactivée globalement | `security.yaml:5` |
| **P2** | 1.8 | Mot de passe MCP dans headers + HTTP sans TLS | `McpHeaderAuthenticator.php` |
| **P2** | 2.3 | Double mécanisme maintenance non documenté | `MaintenanceController.php`, `nginx.conf` |
| **P2** | 2.4 | Provider `email` vs email nullable | `security.yaml:18`, `Profile.php` |
| **P2** | 3.3 | `@nuxtjs/tailwindcss` inutilisé (TW3 vs TW4) | `frontend/package.json:21` |
| **P2** | 5.3 | Doc `VERSION` obsolète | `RELEASE.md`, `CLAUDE.md` |
| **P2** | 5.5 | Données client à la racine (`customer.json`…) | racine projet |
| **P2** | 6.1 | Erreurs avalées sans feedback UI | composables (57+) |
| **P2** | 6.2 | Maintenance check à chaque navigation | `profile.global.ts:34-39` |
| **P2** | 1.12 | `.env.docker.local` tracké malgré gitignore | `infra/dev/.env.docker.local` |
| **P3** | 1.9 | Commentaires+upload en ROLE_VIEWER (à confirmer métier) | `CommentController.php:33` |
| **P3** | 1.10 | Pagination max 2000/1000/500 | `Constructeur.php`, `Document.php` |
| **P3** | 1.11/3.2 | Secrets dev faibles + vars JWT mortes | `infra/dev/.env.docker.local` |
| **P3** | 3.1 | Config morte nuxt.config (5 clés) | `frontend/nuxt.config.ts:56-59` |
| **P3** | 3.4 | Dockerfile dev : blocs commentés Oracle/MySQL | `infra/dev/Dockerfile` |
| **P3** | 3.5 | `node_modules/` orphelin racine + gitignore | racine projet |
| **P3** | 3.6 | 3 fichiers `.js` non typés | `frontend/app/utils/` |
| **P3** | 1.10 | `download()` sans nosniff/CSP | `DocumentServeController.php:105-116` |
| **P3** | 5.4 | Pas de `.env.docker.local.example` | `infra/dev/` |
| **P3** | 7.2 | Image Docker poussée sans smoke test | `build-docker.yml` |
> La dette d'architecture (§4) a son propre plan dans `docs/REVIEW_ARCHITECTURE.md` — recommandation : exécuter enfin sa **Phase 1** (4 corrections effort S, sans impact d'interface).
-343
View File
@@ -1,343 +0,0 @@
# Tickets correctifs — Projet Inventory
> Liste de tâches issues de la review du 2026-06-11 (`REVIEW.md`).
> Chaque ticket est autonome : contexte, ce qu'il faut faire, fichiers concernés.
> Commence par les P0, puis P1, etc. Convention de commit : `fix(T-XXX) : description courte`.
---
## P0 — Urgents (sécurité)
### T-001 — Révoquer et retirer les credentials de `.mcp.json`
**Pourquoi :** le fichier `.mcp.json` est dans git et contient le mot de passe admin MCP de production (`A123`) et un token Lesstime valide. Toute personne avec accès au dépôt (ou à son historique) peut se connecter aux deux systèmes. Supprimer le fichier ne suffit pas : git garde l'historique — il faut **changer les secrets**.
**À faire :**
1. Changer le mot de passe du profil `admin-default-profile` sur `inventory.malio-dev.fr` (et choisir un vrai mot de passe, pas `A123`).
2. Régénérer le bearer token Lesstime côté Lesstime.
3. Sortir le fichier de git sans le supprimer du disque :
```bash
git rm --cached .mcp.json
echo ".mcp.json" >> .gitignore
```
4. Créer `.mcp.json.example` avec des placeholders :
```json
{
"mcpServers": {
"inventory": {
"type": "http",
"url": "https://inventory.malio-dev.fr/_mcp",
"headers": {
"X-Profile-Id": "<PROFILE_ID>",
"X-Profile-Password": "<PASSWORD>"
}
}
}
}
```
5. Remettre les nouveaux secrets dans ton `.mcp.json` local (désormais ignoré).
**Fichiers :** `.mcp.json`, `.gitignore`, `.mcp.json.example` (nouveau)
### T-002 — Supprimer `create_test_user.php` et vérifier la prod
**Pourquoi :** ce script de debug, commité à la racine, crée un compte `ROLE_ADMIN` avec `admin@admin.com` / `admin123` — un mot de passe devinable en quelques essais. S'il a déjà tourné en production, un compte admin faible existe peut-être en ce moment.
**À faire :**
1. Vérifier en prod qu'aucun profil `admin@admin.com` n'est actif :
```sql
SELECT id, email, is_active, roles FROM profiles WHERE email = 'admin@admin.com';
```
S'il existe : le désactiver ou changer son mot de passe immédiatement.
2. Supprimer le script :
```bash
git rm create_test_user.php
```
3. (Optionnel) Si le besoin « créer un admin de dev » existe encore, créer une commande Symfony `app:create-admin` qui prend le mot de passe en argument — ne jamais le hardcoder.
**Fichiers :** `create_test_user.php`
### T-003 — Changer le mot de passe PG prod et archiver les scripts `*-prod-*.php`
**Pourquoi :** 9 scripts dans `scripts/` contiennent le mot de passe de la base de production en clair (`fermerecette`). Ce sont des scripts de réparation one-shot qui ont déjà servi : ils n'ont plus de raison d'être dans le dépôt avec des secrets dedans.
**À faire :**
1. Changer le mot de passe de l'utilisateur PG concerné sur le serveur de prod.
2. Archiver les scripts (le dossier `_archives/` est déjà dans le `.gitignore`) :
```bash
mkdir -p _archives/scripts-prod
git rm scripts/check-prod-values.php scripts/check-prod-audit-dates.php \
scripts/check-prod-missing-piece-cfs.php scripts/check-prod-orphaned-detail.php \
scripts/fix-prod-all.php scripts/fix-prod-recreate-and-migrate.php \
scripts/migrate-orphaned-custom-fields.php scripts/restore-custom-field-values.php \
scripts/verify-prod-health.php
# (les copies locales peuvent aller dans _archives/scripts-prod si tu veux les garder)
```
3. Si l'un d'eux doit rester utilisable : remplacer les credentials en dur par `getenv('DATABASE_URL')`.
**Fichiers :** `scripts/*-prod-*.php`, `scripts/migrate-orphaned-custom-fields.php`, `scripts/restore-custom-field-values.php`
---
## P1 — Importants
### T-004 — Ajouter une CI qui lance tests et lint
**Pourquoi :** aujourd'hui, rien ne lance les tests automatiquement : la CI Gitea ne fait que tagger et builder l'image Docker, et le hook pre-commit est contourné avec `--no-verify` (trop lent). On peut donc livrer en prod avec des tests rouges sans aucune alerte.
**À faire :**
1. Créer `.gitea/workflows/ci.yml` :
```yaml
name: CI
on:
pull_request:
push:
branches: [develop]
jobs:
backend:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: root
POSTGRES_PASSWORD: root
POSTGRES_DB: inventory_test
ports: ["5432:5432"]
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with: { php-version: '8.4', extensions: 'pdo_pgsql, intl' }
- run: composer install --no-interaction --prefer-dist
- run: vendor/bin/php-cs-fixer fix --dry-run --diff --allow-risky=yes
- run: php bin/console doctrine:schema:create --env=test
env: { DATABASE_URL: "postgresql://root:root@localhost:5432/inventory_test" }
- run: vendor/bin/phpunit
env: { DATABASE_URL: "postgresql://root:root@localhost:5432/inventory_test" }
frontend:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22 }
- run: npm ci
working-directory: frontend
- run: npx eslint .
working-directory: frontend
- run: npx nuxi typecheck
working-directory: frontend
- run: npm run build
working-directory: frontend
```
> Adapter les détails (version PHP exacte, env de test) au premier run — l'important est que les 4 vérifications (cs-fixer, PHPUnit, ESLint, typecheck) tournent.
2. Dans Gitea, marquer ce workflow comme requis pour merger une PR vers `develop`.
**Fichiers :** `.gitea/workflows/ci.yml` (nouveau)
### T-005 — Définir une limite de taille d'upload à tous les niveaux
**Pourquoi :** aucune limite n'est choisie nulle part. Conséquence double : en prod, nginx applique son défaut de **1 Mo** (les gros PDF sont sans doute rejetés avec une erreur brute), et côté application rien n'empêcherait de remplir le disque si les limites infra étaient relevées. Décision à prendre : 50 Mo max (à ajuster au métier).
**À faire :**
1. Check applicatif dans `DocumentUploadProcessor::handleMultipartUpload()` (après la validation MIME, ligne ~79) :
```php
private const MAX_UPLOAD_BYTES = 50 * 1024 * 1024; // 50 Mo
if ($file->getSize() > self::MAX_UPLOAD_BYTES) {
throw new BadRequestHttpException('Fichier trop volumineux (max 50 Mo).');
}
```
2. Même check dans `CommentController::create()` dans la boucle `foreach ($files as $file)` (ligne ~106) — renvoyer `$this->json(['message' => 'Fichier trop volumineux (max 50 Mo).'], 400)`.
3. `infra/dev/php.ini` — ajouter :
```ini
upload_max_filesize = 50M
post_max_size = 55M
```
Et vérifier que l'image prod (infra/prod/Dockerfile) reçoit la même config.
4. `infra/prod/nginx.conf` — dans le bloc `server` :
```nginx
client_max_body_size 55m;
```
(idem dans `nginx-proxy.conf` si le proxy frontal est aussi versionné ici).
5. Tester : upload d'un PDF de ~5 Mo en local, et vérifier le message d'erreur propre au-delà de 50 Mo.
**Fichiers :** `src/State/DocumentUploadProcessor.php`, `src/Controller/CommentController.php`, `infra/dev/php.ini`, `infra/prod/nginx.conf`, `infra/prod/Dockerfile`
### T-006 — Corriger le fallback d'URL API du frontend
**Pourquoi :** si la variable `NUXT_PUBLIC_API_BASE_URL` est vide en prod, tous les appels API partent vers `http://localhost:3000` — c'est-à-dire vers la machine de l'utilisateur. L'app casse silencieusement. Un fallback vide = « même origine », ce qui est le comportement correct.
**À faire :**
1. `frontend/app/composables/useApi.ts` ligne 18 :
```ts
// AVANT
const API_BASE_URL = (publicConfig.apiBaseUrl as string) || 'http://localhost:3000'
// APRÈS (chaîne vide = même origine ; useApi ajoute déjà /api)
const API_BASE_URL = (publicConfig.apiBaseUrl as string) || ''
```
2. `frontend/nuxt.config.ts` ligne ~49 : remplacer le fallback `'http://localhost/api'` par `''` (valeur SSR jamais utilisée, SSR off — autant ne pas mentir).
3. Vérifier en dev que tout fonctionne encore (la variable est définie en dev, donc rien ne doit changer), puis `npx nuxi typecheck`.
**Fichiers :** `frontend/app/composables/useApi.ts`, `frontend/nuxt.config.ts`
### T-007 — Purger la doc « submodule » (le frontend est dans le monorepo)
**Pourquoi :** le frontend a été rapatrié dans le repo principal, mais README, DEPLOY, RELEASE et le README frontend décrivent encore le clonage `--recurse-submodules` et le workflow de commit en deux temps. Un nouveau dev (ou toi en incident de prod) suit des étapes qui n'existent plus.
**À faire :**
1. `README.md` : lignes 49-50 → `git clone <url-du-repo>` ; section « workflow » lignes ~291-296 → décrire « un seul commit depuis la racine couvre backend + frontend ».
2. `DEPLOY.md` : supprimer les `git submodule update --init --recursive` (lignes 58, 62, 208).
3. `RELEASE.md` : supprimer les étapes submodule (lignes 49, 117).
4. `frontend/README.md` : remplacer la section « submodule » (lignes 150-154) par « ce dossier fait partie du monorepo Inventory ».
**Fichiers :** `README.md`, `DEPLOY.md`, `RELEASE.md`, `frontend/README.md`
### T-008 — Corriger l'utilisateur PG dans `DEPLOY.md`
**Pourquoi :** les commandes de DEPLOY.md utilisent `ferme_user` / `fermerecette` — copié-collé du projet Ferme. Le vrai utilisateur est `inventory_user`. En situation d'incident, suivre la doc ferait taper des commandes qui échouent ou visent la mauvaise base.
**À faire :**
1. Remplacer les 6 occurrences de `ferme_user` par `inventory_user`.
2. Remplacer le mot de passe en clair par un placeholder `<mot-de-passe-prod>` (le mot de passe n'a rien à faire dans la doc).
**Fichiers :** `DEPLOY.md`
### T-009 — Durcir le garde anti path-traversal de `DocumentStorageService`
**Pourquoi :** le contrôle `realpath()` (qui vérifie que le chemin final est bien dans le dossier de stockage) est sauté quand le fichier n'existe pas, car `realpath()` renvoie `false` dans ce cas. Le risque actuel est faible (le chemin vient de la base, pas de l'utilisateur), mais le contrôle se veut une protection — autant qu'il protège vraiment.
**À faire :** dans `getAbsolutePath()` (`src/Service/DocumentStorageService.php:28-42`) :
```php
// AVANT
$absolutePath = $this->storageDir.'/'.$relativePath;
$realPath = realpath($absolutePath);
if (false !== $realPath && !str_starts_with($realPath, realpath($this->storageDir))) {
throw new RuntimeException(sprintf('Path traversal detected: "%s"', $relativePath));
}
// APRÈS — valider sur le dossier parent, qui existe toujours pour un fichier servi
$absolutePath = $this->storageDir.'/'.$relativePath;
$realParent = realpath(dirname($absolutePath));
$realStorage = realpath($this->storageDir);
if (false === $realStorage || false === $realParent
|| !str_starts_with($realParent.'/', $realStorage.'/')) {
throw new RuntimeException(sprintf('Path traversal detected: "%s"', $relativePath));
}
```
Garder le check `str_contains($relativePath, '..')` existant en première ligne. Ajouter un test unitaire avec un chemin contenant `../` et un chemin absolu.
**Fichiers :** `src/Service/DocumentStorageService.php`, `tests/` (nouveau test)
---
## P2 — Consolidation
### T-010 — Hardening CSRF : header `X-Requested-With` obligatoire sur les écritures
**Pourquoi :** l'auth est par cookie, et la seule protection contre le CSRF (un site malveillant qui fait faire des requêtes à ton navigateur connecté) est l'attribut `SameSite=Lax` du cookie. Une deuxième barrière peu coûteuse : exiger un header custom, qu'un formulaire HTML cross-site ne peut pas envoyer.
**À faire :**
1. Côté front, dans `useApi.ts`, ajouter `'X-Requested-With': 'XMLHttpRequest'` aux headers de toutes les requêtes.
2. Côté back, créer un listener `kernel.request` qui renvoie 403 si la méthode n'est pas GET/HEAD/OPTIONS, que le chemin matche `^/api` (hors `/api/session/profile` pour le login) et que le header est absent.
3. Supprimer la clé morte `csrfToken` de `nuxt.config.ts` (elle laisse croire qu'une protection CSRF existe).
4. Adapter les tests API (`AbstractApiTestCase`) pour envoyer le header.
**Fichiers :** `frontend/app/composables/useApi.ts`, `src/EventListener/` (nouveau), `frontend/nuxt.config.ts`, `tests/AbstractApiTestCase.php`
### T-011 — Test fonctionnel : l'ID de session change au login
**Pourquoi :** la protection automatique contre la fixation de session est désactivée (`session_fixation_strategy: none`, choix documenté pour la SPA) et compensée par un `$session->migrate(true)` manuel au login. Si quelqu'un supprime ce `migrate` un jour, plus rien ne protège — un test doit le verrouiller.
**À faire :** écrire un test API qui : récupère le cookie de session avant login → se loggue → vérifie que l'ID de session a changé. Ajouter un commentaire dans `SessionProfileController::activateProfile()` pointant vers `security.yaml:5`.
**Fichiers :** `tests/Api/SessionProfileTest.php` (ou équivalent), `src/Controller/SessionProfileController.php`
### T-012 — MCP : remplacer le mot de passe par un token Bearer + HTTPS
**Pourquoi :** le mot de passe du profil circule en clair dans les headers de chaque requête MCP (et l'URL configurée est en `http://`). Les headers finissent dans les logs des proxys. Un token dédié révocable, transmis en `Authorization: Bearer`, est le pattern déjà utilisé par Lesstime.
**À faire :** ajouter un champ `mcpToken` (hashé) sur Profile ou une table `api_tokens`, générer via une commande console, adapter `McpHeaderAuthenticator` pour valider le Bearer (garder le rate-limiting), mettre à jour `.mcp.json.example`, et servir `/_mcp` en HTTPS uniquement.
**Fichiers :** `src/Mcp/Security/McpHeaderAuthenticator.php`, `src/Entity/Profile.php` ou nouvelle entité, migration, `.mcp.json.example`
### T-013 — Maintenance : faire respecter le flag côté backend
**Pourquoi :** le toggle admin écrit `var/maintenance`, mais seul le **frontend** (middleware) le vérifie. Quelqu'un qui appelle l'API directement (curl, MCP, script) passe à travers. Et le second mécanisme (`maintenance.on` lu par nginx, posé par `deploy.sh`) n'est documenté nulle part.
**À faire :**
1. Listener `kernel.request` backend : si `var/maintenance` existe et que l'utilisateur n'est pas admin → 503 JSON (sauf `/api/maintenance/check` et `/api/session/*`).
2. Documenter les deux niveaux (applicatif vs nginx/deploy) dans `DEPLOY.md`.
**Fichiers :** `src/EventListener/` (nouveau), `DEPLOY.md`
### T-014 — Résoudre l'incohérence provider/email nullable
**Pourquoi :** le user provider Symfony charge les profils par `email`, mais l'email est nullable (profils « kiosque »). Ça marche par chance (l'authenticator charge par ID), mais tout futur usage du provider standard cassera sur ces profils.
**À faire :** décision à prendre — option A : email obligatoire + email technique généré pour les kiosques ; option B : provider custom qui charge par id ou email. Documenter le choix dans `docs/BACKEND.md`.
**Fichiers :** `config/packages/security.yaml`, `src/Entity/Profile.php`, éventuellement `src/Security/`
### T-015 — Supprimer `@nuxtjs/tailwindcss`
**Pourquoi :** dépendance non utilisée (le projet utilise `@tailwindcss/vite`, Tailwind 4) qui installe en plus un Tailwind 3 parallèle — bloat et risque de conflit de résolution.
**À faire :**
```bash
cd frontend && npm uninstall @nuxtjs/tailwindcss && npm run build && npx nuxi typecheck
```
**Fichiers :** `frontend/package.json`, `frontend/package-lock.json`
### T-016 — Mettre à jour la doc de versioning (`VERSION` → `config/version.yaml`)
**Pourquoi :** `RELEASE.md` (lignes 17, 50, 80, 82) et l'arbre projet de `CLAUDE.md` référencent un fichier `VERSION` qui n'existe plus — la version vit dans `config/version.yaml`.
**À faire :** remplacer toutes les références ; vérifier au passage que la description du footer frontend (« lit VERSION au build ») correspond au mécanisme réel.
**Fichiers :** `RELEASE.md`, `CLAUDE.md`
### T-017 — Sortir les données client de la racine + gitignore défensif
**Pourquoi :** `customer.json`, `Company (1).json`, `Ensemble simple rotor.pdf` (données client réelles → enjeu RGPD) traînent à la racine, non protégés par le `.gitignore` : un `git add .` distrait les commiterait.
**À faire :**
1. Déplacer ces fichiers hors du dépôt (ex. `~/imports/inventory/`). Supprimer aussi `inventory_prod (2).sql.gz` et le `node_modules/` orphelin de la racine.
2. Ajouter au `.gitignore` racine :
```
/node_modules/
/*.json
/*.pdf
```
(les `.json` légitimes du projet sont dans des sous-dossiers ou explicitement trackés — `composer.json` etc. restent suivis car déjà trackés ; pour les nouveaux, `git add -f` reste possible).
**Fichiers :** `.gitignore`, racine du projet
### T-018 — Uniformiser la gestion d'erreur frontend (état d'erreur au lieu de `console.error`)
**Pourquoi :** en cas d'échec de chargement, le pattern actuel est `console.error(...)` puis la page s'affiche à moitié vide, sans message. L'utilisateur ne sait pas que quelque chose a raté ni comment réessayer.
**À faire :** règle : `useApi` est le seul à toaster ; les composables exposent un `error: Ref<string | null>` que la page affiche (bandeau avec bouton Réessayer). Commencer par les 3 pages principales : détail machine (`useMachineDetailData.ts:372,385`), détail composant, détail pièce. Étendre ensuite au reste.
**Fichiers :** `frontend/app/composables/useMachineDetailData.ts`, `useComponentEdit.ts`, `usePieceEdit.ts`, pages correspondantes
### T-019 — Cacher le résultat de `/maintenance/check` (TTL)
**Pourquoi :** chaque navigation d'un non-admin déclenche un appel API pour vérifier la maintenance — de la latence sur toutes les transitions de page pour un état qui ne change presque jamais.
**À faire :** dans `profile.global.ts`, stocker le résultat dans un `useState` avec timestamp et ne re-fetcher que si > 60 s.
**Fichiers :** `frontend/app/middleware/profile.global.ts`
### T-020 — Détracker `infra/dev/.env.docker.local` + fournir un `.example`
**Pourquoi :** le fichier est dans le `.gitignore` mais a été commité avant l'ajout de la règle — git continue donc de le suivre. Chaque dev qui le modifie crée du diff, et ses secrets (même de dev) sont versionnés.
**À faire :**
```bash
cp infra/dev/.env.docker.local infra/dev/.env.docker.local.example
# Dans le .example : remplacer les valeurs par des placeholders <CHANGE_ME>
# et supprimer les variables JWT_* (inutilisées, cf. T-023)
git rm --cached infra/dev/.env.docker.local
git add infra/dev/.env.docker.local.example
```
Mettre à jour le README (section installation) pour mentionner la copie du `.example`.
**Fichiers :** `infra/dev/.env.docker.local`, `infra/dev/.env.docker.local.example` (nouveau), `README.md`
---
## P3 — Nice to have
### T-021 — Décision métier : commentaires (et pièces jointes) en `ROLE_VIEWER` ?
**Pourquoi :** seule écriture accessible aux lecteurs. Si c'est voulu (« tout le monde commente »), le documenter dans `docs/BACKEND.md` ; sinon passer à `ROLE_GESTIONNAIRE` (`CommentController.php:33`).
**Fichiers :** `src/Controller/CommentController.php`, `docs/BACKEND.md`
### T-022 — Revoir les pagination max (2000/1000/500)
**Pourquoi :** `Constructeur` (2000), `ConstructeurCategorie` (1000), `Document` (500) — vérifier le besoin réel du front et redescendre, ou commenter pourquoi.
**Fichiers :** `src/Entity/Constructeur.php`, `src/Entity/ConstructeurCategorie.php`, `src/Entity/Document.php`
### T-023 — Nettoyer les variables JWT et les secrets `changeme`
**Pourquoi :** `JWT_SECRET_KEY`/`JWT_PUBLIC_KEY`/`JWT_PASSPHRASE` ne servent à rien (auth session). `APP_SECRET=changeme_…` mérite une vraie valeur aléatoire locale. (Fusionne naturellement avec T-020.)
**Fichiers :** `infra/dev/.env.docker.local`
### T-024 — Supprimer la config morte de `nuxt.config.ts`
**Pourquoi :** `csrfToken`, `requestTimeout`, `enableDebug`, `enableAnalytics`, `logLevel` ne sont consommés nulle part — de la config qui ment. (`csrfToken` est déjà traité par T-010.)
**Fichiers :** `frontend/nuxt.config.ts:56-59`
### T-025 — Nettoyer le Dockerfile dev (blocs Oracle/IMAP/MySQL commentés)
**Pourquoi :** restes d'un template générique sans rapport avec un projet PostgreSQL.
**Fichiers :** `infra/dev/Dockerfile:48-53,82-100`
### T-026 — Renommer les 3 utils `.js` en `.ts`
**Pourquoi :** `documentPreview.js`, `fileIcons.js`, `printTemplates/machineReport.js` sont importés depuis du TypeScript sans types. Tâche mécanique (bon candidat pour Codex).
**Fichiers :** `frontend/app/utils/documentPreview.js`, `frontend/app/utils/fileIcons.js`, `frontend/app/utils/printTemplates/machineReport.js`
### T-027 — Ajouter `nosniff` + `CSP: sandbox` à `download()`
**Pourquoi :** `serve()` envoie ces deux headers de protection, `download()` non — asymétrie gratuite.
**À faire :** copier les deux `headers->set(...)` de `serve()` dans `download()` (`DocumentServeController.php:110-116`).
**Fichiers :** `src/Controller/DocumentServeController.php`
### T-028 — Smoke test de l'image Docker avant push
**Pourquoi :** `build-docker.yml` pousse `latest` sans vérifier que l'image démarre.
**À faire :** entre build et push : `docker run --rm gitea.malio.fr/malio-dev/inventory:${{ gitea.ref_name }} php bin/console about`.
**Fichiers :** `.gitea/workflows/build-docker.yml`
> **Hors tickets :** la dette d'architecture (smartMatch dupliqué, double flush, `pendingStructure`, God controller à 1121 lignes, `any` ×179…) a déjà son plan d'action chiffré dans `docs/REVIEW_ARCHITECTURE.md`. Recommandation forte : exécuter sa **Phase 1** (4 corrections effort S, sans impact d'interface) avant qu'elle ne prenne encore 3 mois — voir `REVIEW.md` §4.
---
## Résumé
| Priorité | Tickets | Estimation |
|----------|---------|------------|
| **P0** | T-001 à T-003 | ~2h (+ rotations de secrets) |
| **P1** | T-004 à T-009 | ~1,5 j |
| **P2** | T-010 à T-020 | ~2,5 j |
| **P3** | T-021 à T-028 | ~0,5 j |
| **Total** | 28 tickets | ~5 j |
> Commence par **T-001** — tant que les secrets ne sont pas révoqués, tout le reste est secondaire.
> Pour chaque ticket, fais un commit dédié avec le numéro dans le message (ex. `fix(T-001) : retirer .mcp.json du dépôt`).
+1
View File
@@ -16,6 +16,7 @@
"nyholm/psr7": "^1.8",
"phpdocumentor/reflection-docblock": "^5.6",
"phpstan/phpdoc-parser": "^2.3",
"sentry/sentry-symfony": "^5.10",
"symfony/asset": "8.0.*",
"symfony/console": "8.0.*",
"symfony/dotenv": "8.0.*",
Generated
+419 -1
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "5c54b1589d9e815f4c9b7e5e1d2d69c7",
"content-hash": "beb5fa2114e6597caebb6a098de494c1",
"packages": [
{
"name": "api-platform/doctrine-common",
@@ -2361,6 +2361,185 @@
},
"time": "2025-10-26T09:35:14+00:00"
},
{
"name": "guzzlehttp/psr7",
"version": "2.12.3",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "7ec62dc3f44aa218487dbed81a9bf9bc647be55d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/7ec62dc3f44aa218487dbed81a9bf9bc647be55d",
"reference": "7ec62dc3f44aa218487dbed81a9bf9bc647be55d",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0",
"psr/http-factory": "^1.0",
"psr/http-message": "^1.1 || ^2.0",
"ralouphie/getallheaders": "^3.0",
"symfony/deprecation-contracts": "^2.5 || ^3.0",
"symfony/polyfill-php80": "^1.25"
},
"provide": {
"psr/http-factory-implementation": "1.0",
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"http-interop/http-factory-tests": "1.1.0",
"jshttp/mime-db": "1.54.0.1",
"phpunit/phpunit": "^8.5.52 || ^9.6.34"
},
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
},
"type": "library",
"extra": {
"bamarni-bin": {
"bin-links": true,
"forward-command": false
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Psr7\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "George Mponos",
"email": "gmponos@gmail.com",
"homepage": "https://github.com/gmponos"
},
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com",
"homepage": "https://github.com/Nyholm"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://github.com/sagikazarmark"
},
{
"name": "Tobias Schultze",
"email": "webmaster@tubo-world.de",
"homepage": "https://github.com/Tobion"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://sagikazarmark.hu"
}
],
"description": "PSR-7 message implementation that also provides common utility methods",
"keywords": [
"http",
"message",
"psr-7",
"request",
"response",
"stream",
"uri",
"url"
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/2.12.3"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://github.com/Nyholm",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
"type": "tidelift"
}
],
"time": "2026-06-23T15:21:08+00:00"
},
{
"name": "jean85/pretty-package-versions",
"version": "2.1.1",
"source": {
"type": "git",
"url": "https://github.com/Jean85/pretty-package-versions.git",
"reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a",
"reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a",
"shasum": ""
},
"require": {
"composer-runtime-api": "^2.1.0",
"php": "^7.4|^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.2",
"jean85/composer-provided-replaced-stub-package": "^1.0",
"phpstan/phpstan": "^2.0",
"phpunit/phpunit": "^7.5|^8.5|^9.6",
"rector/rector": "^2.0",
"vimeo/psalm": "^4.3 || ^5.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Jean85\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Alessandro Lai",
"email": "alessandro.lai85@gmail.com"
}
],
"description": "A library to get pretty versions strings of installed dependencies",
"keywords": [
"composer",
"package",
"release",
"versions"
],
"support": {
"issues": "https://github.com/Jean85/pretty-package-versions/issues",
"source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1"
},
"time": "2025-03-19T14:43:43+00:00"
},
{
"name": "mcp/sdk",
"version": "v0.4.0",
@@ -3701,6 +3880,245 @@
},
"time": "2024-09-11T13:17:53+00:00"
},
{
"name": "ralouphie/getallheaders",
"version": "3.0.3",
"source": {
"type": "git",
"url": "https://github.com/ralouphie/getallheaders.git",
"reference": "120b605dfeb996808c31b6477290a714d356e822"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
"reference": "120b605dfeb996808c31b6477290a714d356e822",
"shasum": ""
},
"require": {
"php": ">=5.6"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.1",
"phpunit/phpunit": "^5 || ^6.5"
},
"type": "library",
"autoload": {
"files": [
"src/getallheaders.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ralph Khattar",
"email": "ralph.khattar@gmail.com"
}
],
"description": "A polyfill for getallheaders.",
"support": {
"issues": "https://github.com/ralouphie/getallheaders/issues",
"source": "https://github.com/ralouphie/getallheaders/tree/develop"
},
"time": "2019-03-08T08:55:37+00:00"
},
{
"name": "sentry/sentry",
"version": "4.28.0",
"source": {
"type": "git",
"url": "https://github.com/getsentry/sentry-php.git",
"reference": "662cb7a01a342a7f33780fc955ff4a028d8b785a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/getsentry/sentry-php/zipball/662cb7a01a342a7f33780fc955ff4a028d8b785a",
"reference": "662cb7a01a342a7f33780fc955ff4a028d8b785a",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*",
"guzzlehttp/psr7": "^1.8.4|^2.1.1",
"jean85/pretty-package-versions": "^1.5|^2.0.4",
"php": "^7.2|^8.0",
"psr/log": "^1.0|^2.0|^3.0",
"symfony/options-resolver": "^4.4.30|^5.0.11|^6.0|^7.0|^8.0"
},
"conflict": {
"raven/raven": "*"
},
"require-dev": {
"carthage-software/mago": "1.30.0",
"friendsofphp/php-cs-fixer": "^3.4",
"guzzlehttp/promises": "^2.0.3",
"monolog/monolog": "^1.6|^2.0|^3.0",
"nyholm/psr7": "^1.8",
"open-telemetry/api": "^1.0",
"open-telemetry/exporter-otlp": "^1.0",
"open-telemetry/sdk": "^1.0",
"phpstan/phpstan": "^1.3",
"phpunit/phpunit": "^8.5.52|^9.6.34",
"spiral/roadrunner-http": "^3.6",
"spiral/roadrunner-worker": "^3.6"
},
"suggest": {
"ext-excimer": "Enable Sentry profiling with the Excimer PHP extension.",
"monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler."
},
"type": "library",
"autoload": {
"files": [
"src/functions.php"
],
"psr-4": {
"Sentry\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Sentry",
"email": "accounts@sentry.io"
}
],
"description": "PHP SDK for Sentry (http://sentry.io)",
"homepage": "http://sentry.io",
"keywords": [
"crash-reporting",
"crash-reports",
"error-handler",
"error-monitoring",
"log",
"logging",
"profiling",
"sentry",
"tracing"
],
"support": {
"issues": "https://github.com/getsentry/sentry-php/issues",
"source": "https://github.com/getsentry/sentry-php/tree/4.28.0"
},
"funding": [
{
"url": "https://sentry.io/",
"type": "custom"
},
{
"url": "https://sentry.io/pricing/",
"type": "custom"
}
],
"time": "2026-06-11T12:22:38+00:00"
},
{
"name": "sentry/sentry-symfony",
"version": "5.10.0",
"source": {
"type": "git",
"url": "https://github.com/getsentry/sentry-symfony.git",
"reference": "6f49255f4cdcfc43a3a283bd3a1f65d483e9192f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/getsentry/sentry-symfony/zipball/6f49255f4cdcfc43a3a283bd3a1f65d483e9192f",
"reference": "6f49255f4cdcfc43a3a283bd3a1f65d483e9192f",
"shasum": ""
},
"require": {
"guzzlehttp/psr7": "^2.1.1",
"jean85/pretty-package-versions": "^1.5||^2.0",
"php": "^7.2||^8.0",
"sentry/sentry": "^4.23.0",
"symfony/cache-contracts": "^1.1||^2.4||^3.0",
"symfony/config": "^4.4.20||^5.0.11||^6.0||^7.0||^8.0",
"symfony/console": "^4.4.20||^5.0.11||^6.0||^7.0||^8.0",
"symfony/dependency-injection": "^4.4.20||^5.0.11||^6.0||^7.0||^8.0",
"symfony/event-dispatcher": "^4.4.20||^5.0.11||^6.0||^7.0||^8.0",
"symfony/http-kernel": "^4.4.20||^5.0.11||^6.0||^7.0||^8.0",
"symfony/polyfill-php80": "^1.22",
"symfony/psr-http-message-bridge": "^1.2||^2.0||^6.4||^7.0||^8.0",
"symfony/yaml": "^4.4.20||^5.0.11||^6.0||^7.0||^8.0"
},
"require-dev": {
"doctrine/dbal": "^2.13||^3.3||^4.0",
"doctrine/doctrine-bundle": "^2.6||^3.0",
"friendsofphp/php-cs-fixer": "^2.19||^3.40",
"masterminds/html5": "^2.8",
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "1.12.5",
"phpstan/phpstan-phpunit": "1.4.0",
"phpstan/phpstan-symfony": "1.4.10",
"phpunit/phpunit": "^8.5.40||^9.6.21",
"symfony/browser-kit": "^4.4.20||^5.0.11||^6.0||^7.0||^8.0",
"symfony/cache": "^4.4.20||^5.0.11||^6.0||^7.0||^8.0",
"symfony/dom-crawler": "^4.4.20||^5.0.11||^6.0||^7.0||^8.0",
"symfony/framework-bundle": "^4.4.20||^5.0.11||^6.0||^7.0||^8.0",
"symfony/http-client": "^4.4.20||^5.0.11||^6.0||^7.0||^8.0",
"symfony/messenger": "^4.4.20||^5.0.11||^6.0||^7.0||^8.0",
"symfony/monolog-bundle": "^3.4||^4.0",
"symfony/phpunit-bridge": "^5.2.6||^6.0||^7.0||^8.0",
"symfony/process": "^4.4.20||^5.0.11||^6.0||^7.0||^8.0",
"symfony/security-core": "^4.4.20||^5.0.11||^6.0||^7.0||^8.0",
"symfony/security-http": "^4.4.20||^5.0.11||^6.0||^7.0||^8.0",
"symfony/twig-bundle": "^4.4.20||^5.0.11||^6.0||^7.0||^8.0",
"vimeo/psalm": "^4.3||^5.16.0"
},
"suggest": {
"doctrine/doctrine-bundle": "Allow distributed tracing of database queries using Sentry.",
"monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler.",
"symfony/cache": "Allow distributed tracing of cache pools using Sentry.",
"symfony/twig-bundle": "Allow distributed tracing of Twig template rendering using Sentry."
},
"type": "symfony-bundle",
"autoload": {
"files": [
"src/aliases.php"
],
"psr-4": {
"Sentry\\SentryBundle\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Sentry",
"email": "accounts@sentry.io"
}
],
"description": "Symfony integration for Sentry (http://getsentry.com)",
"homepage": "http://getsentry.com",
"keywords": [
"errors",
"logging",
"sentry",
"symfony"
],
"support": {
"issues": "https://github.com/getsentry/sentry-symfony/issues",
"source": "https://github.com/getsentry/sentry-symfony/tree/5.10.0"
},
"funding": [
{
"url": "https://sentry.io/",
"type": "custom"
},
{
"url": "https://sentry.io/pricing/",
"type": "custom"
}
],
"time": "2026-04-01T14:50:32+00:00"
},
{
"name": "symfony/asset",
"version": "v8.0.0",
+2
View File
@@ -7,6 +7,7 @@ use DAMA\DoctrineTestBundle\DAMADoctrineTestBundle;
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
use Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle;
use Nelmio\CorsBundle\NelmioCorsBundle;
use Sentry\SentryBundle\SentryBundle;
use Symfony\AI\McpBundle\McpBundle;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\MonologBundle\MonologBundle;
@@ -24,4 +25,5 @@ return [
DAMADoctrineTestBundle::class => ['test' => true],
McpBundle::class => ['all' => true],
MonologBundle::class => ['all' => true],
SentryBundle::class => ['prod' => true],
];
+35
View File
@@ -0,0 +1,35 @@
# Error tracking → GlitchTip (compatible SDK Sentry).
# Actif uniquement en prod (bundle enregistré prod-only dans bundles.php).
# Si SENTRY_DSN est vide/non défini, le SDK est inerte (rien n'est envoyé).
when@prod:
parameters:
# Valeur par défaut : DSN vide => Sentry désactivé tant qu'il n'est pas fourni.
env(SENTRY_DSN): ''
sentry:
dsn: '%env(SENTRY_DSN)%'
# Capture des erreurs fatales PHP via le handler. On DÉSACTIVE le listener
# kernel pour éviter les doublons avec le handler Monolog (ci-dessous) : les
# exceptions du kernel sont déjà logguées par Symfony => remontées via Monolog.
register_error_listener: false
register_error_handler: true
options:
environment: '%env(APP_ENV)%'
release: '%app.version%'
# Pas d'APM/tracing (DuckDB hors périmètre du ticket #146).
traces_sample_rate: 0.0
# Ne pas remonter les 4xx HTTP comme des erreurs (bruit).
ignore_exceptions:
- Symfony\Component\HttpKernel\Exception\NotFoundHttpException
- Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException
- Symfony\Component\Security\Core\Exception\AccessDeniedException
# Handler Monolog -> Sentry : remonte les logs niveau ERROR+ comme Issues GlitchTip
# (en plus des erreurs fatales). Les $logger->error(...) métier deviennent des Issues.
# Le filtre ignore_exceptions ci-dessus s'applique aussi à ces événements.
services:
Sentry\Monolog\Handler:
arguments:
$hub: '@Sentry\State\HubInterface'
$level: !php/const Monolog\Level::Error
$bubble: true
+5
View File
@@ -4,6 +4,11 @@
# Files in the packages/ subdirectory configure your dependencies.
# See also https://symfony.com/doc/current/service_container/import.html
# Expose le paramètre app.version (source unique, bumpé par le script de release) au
# container — utilisé notamment comme "release" Sentry/GlitchTip dans sentry.yaml.
imports:
- { resource: version.yaml }
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
+1 -1
View File
@@ -1,2 +1,2 @@
parameters:
app.version: '1.9.49'
app.version: '1.9.52'
+23 -2
View File
@@ -41,7 +41,20 @@ export default defineNuxtConfig({
lucide: () => import('@iconify-json/lucide/icons.json').then(i => i.default)
}
}
]
],
// Error tracking → GlitchTip. Module chargé uniquement si un DSN est fourni
// (build prod) ; en dev sans DSN, aucun overhead Sentry. Les options d'upload des
// source maps sont passées en ligne (fournies au build via secrets CI).
...(process.env.NUXT_PUBLIC_SENTRY_DSN
? [['@sentry/nuxt/module', {
sourceMapsUploadOptions: {
url: process.env.SENTRY_URL,
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN
}
}] as [string, Record<string, unknown>]]
: [])
],
runtimeConfig: {
apiBaseUrl: process.env.NUXT_API_BASE_URL
@@ -57,9 +70,17 @@ export default defineNuxtConfig({
enableDebug: process.env.NUXT_PUBLIC_ENABLE_DEBUG || 'false',
enableAnalytics: process.env.NUXT_PUBLIC_ENABLE_ANALYTICS || 'false',
csrfToken: process.env.NUXT_PUBLIC_CSRF_TOKEN || '',
logLevel: process.env.NUXT_PUBLIC_LOG_LEVEL || 'warn'
logLevel: process.env.NUXT_PUBLIC_LOG_LEVEL || 'warn',
sentry: {
// DSN du projet GlitchTip "inventory-front" (vide => SDK inerte).
dsn: process.env.NUXT_PUBLIC_SENTRY_DSN || '',
environment: process.env.NODE_ENV || 'development'
}
}
},
// Source maps "hidden" : générées et uploadées vers GlitchTip pour des stacktraces
// lisibles, sans exposer les .map au navigateur.
sourcemap: { client: 'hidden' },
vite: {
plugins: [tailwindcss()],
server: {
+839 -15
View File
@@ -8,6 +8,7 @@
"hasInstallScript": true,
"dependencies": {
"@nuxtjs/tailwindcss": "^6.14.0",
"@sentry/nuxt": "^10.61.0",
"@tailwindcss/vite": "^4.1.11",
"daisyui": "^5.0.48",
"nuxt": "^4.0.1",
@@ -70,6 +71,64 @@
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@apm-js-collab/code-transformer": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/@apm-js-collab/code-transformer/-/code-transformer-0.15.0.tgz",
"integrity": "sha512-XmXYVs8CzJ1Aj79noVbn2weUO/XWtRyURpGqx7aU7DOXlUQhR0WKOQNF0okh7PCeY37vxf7kU3v57OAkEPm3ww==",
"license": "Apache-2.0",
"dependencies": {
"@types/estree": "^1.0.8",
"astring": "^1.9.0",
"esquery": "^1.7.0",
"meriyah": "^6.1.4",
"semifies": "^1.0.0",
"source-map": "^0.6.0"
},
"bin": {
"code-transformer": "cli.js"
}
},
"node_modules/@apm-js-collab/code-transformer-bundler-plugins": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@apm-js-collab/code-transformer-bundler-plugins/-/code-transformer-bundler-plugins-0.5.0.tgz",
"integrity": "sha512-YxLBY5nGlurL7QeJLq6e5g0ouBpAp0pwgyA/5rHXEXwhiPLn9ZHbT+Y2LlP90GT872cSocfjWRYu/fnpuBudNQ==",
"license": "MIT",
"dependencies": {
"@apm-js-collab/code-transformer": "^0.15.0",
"es-module-lexer": "^2.1.0",
"magic-string": "^0.30.21",
"module-details-from-path": "^1.0.4"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@apm-js-collab/code-transformer-bundler-plugins/node_modules/es-module-lexer": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.2.0.tgz",
"integrity": "sha512-3lGxdTXCLfe1MYfTz1y2ksAAUM4NAOP6rPEjxGJVKO7TZ5+tvHCaQWGpC4Y3IXvW3ece0Cz1cIP4FWBxOnGCTQ==",
"license": "MIT"
},
"node_modules/@apm-js-collab/code-transformer/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/@apm-js-collab/tracing-hooks": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@apm-js-collab/tracing-hooks/-/tracing-hooks-0.10.0.tgz",
"integrity": "sha512-2/Z3NTewJTruUkmsSnBC5bJlLNUd9keuD1OLlTEpim4FyLhm6m2Rnfv+wrFdUvFfhmH8CRdiDZBqBrn+wyaGuA==",
"license": "Apache-2.0",
"dependencies": {
"@apm-js-collab/code-transformer": "^0.15.0",
"debug": "^4.4.1",
"module-details-from-path": "^1.0.4"
}
},
"node_modules/@babel/code-frame": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
@@ -2133,6 +2192,103 @@
"dev": true,
"license": "MIT"
},
"node_modules/@opentelemetry/api": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz",
"integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==",
"license": "Apache-2.0",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/@opentelemetry/api-logs": {
"version": "0.214.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.214.0.tgz",
"integrity": "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA==",
"license": "Apache-2.0",
"dependencies": {
"@opentelemetry/api": "^1.3.0"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/@opentelemetry/core": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.8.0.tgz",
"integrity": "sha512-hd1Lfh8p545nNz+jq1Ejfz+Mn1hyLuxYn1YzTfFNrxr8urEWMNQLPf1Th8kjOH+HxwawCrtgBp8JpBUR4ZSgww==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/semantic-conventions": "^1.29.0"
},
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.0.0 <1.10.0"
}
},
"node_modules/@opentelemetry/instrumentation": {
"version": "0.214.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.214.0.tgz",
"integrity": "sha512-MHqEX5Dk59cqVah5LiARMACku7jXSVk9iVDWOea4x3cr7VfdByeDCURK6o1lntT1JS/Tsovw01UJrBhN3/uC5w==",
"license": "Apache-2.0",
"dependencies": {
"@opentelemetry/api-logs": "0.214.0",
"import-in-the-middle": "^3.0.0",
"require-in-the-middle": "^8.0.0"
},
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
"peerDependencies": {
"@opentelemetry/api": "^1.3.0"
}
},
"node_modules/@opentelemetry/resources": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.8.0.tgz",
"integrity": "sha512-qmXQ27ilDbUK/vGMqwL8D4/rhn76C+sherM4wTbjlfknR8Nvfc/hCxjRJPhkzZzUsPiNg16SA31NxMabwttRjg==",
"license": "Apache-2.0",
"dependencies": {
"@opentelemetry/core": "2.8.0",
"@opentelemetry/semantic-conventions": "^1.29.0"
},
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.3.0 <1.10.0"
}
},
"node_modules/@opentelemetry/sdk-trace-base": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.8.0.tgz",
"integrity": "sha512-mhU4jp+vW0mGbFRd+GeXHvmfA4aDqWjBjLC3pE5XMpLs0IE2ryYb019Ts2AQrOq67gaTF25D91+fgvEHDZEnuQ==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@opentelemetry/core": "2.8.0",
"@opentelemetry/resources": "2.8.0",
"@opentelemetry/semantic-conventions": "^1.29.0"
},
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.3.0 <1.10.0"
}
},
"node_modules/@opentelemetry/semantic-conventions": {
"version": "1.41.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz",
"integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==",
"license": "Apache-2.0",
"engines": {
"node": ">=14"
}
},
"node_modules/@oxc-minify/binding-android-arm64": {
"version": "0.87.0",
"resolved": "https://registry.npmjs.org/@oxc-minify/binding-android-arm64/-/binding-android-arm64-0.87.0.tgz",
@@ -3747,6 +3903,602 @@
"dev": true,
"license": "MIT"
},
"node_modules/@sentry/babel-plugin-component-annotate": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-5.3.0.tgz",
"integrity": "sha512-p4q8gn8wcFqZGP/s2MnJCAAd8fTikaU6A0mM97RDHQgStcrYiaS0Sc5zUNfb1V+UOLPuvdEdL6MwyxfzjYJQTA==",
"license": "MIT",
"engines": {
"node": ">= 18"
}
},
"node_modules/@sentry/browser": {
"version": "10.62.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.62.0.tgz",
"integrity": "sha512-uJi0yPssB3Nt/cZ8/S8opW42gaM59/6IyNtPFYD7C0ciudi/nIo5QMVpCYBBI3jnKFOIQLlsMT4pDlOLuxxNuQ==",
"license": "MIT",
"dependencies": {
"@sentry/browser-utils": "10.62.0",
"@sentry/core": "10.62.0",
"@sentry/feedback": "10.62.0",
"@sentry/replay": "10.62.0",
"@sentry/replay-canvas": "10.62.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/browser-utils": {
"version": "10.62.0",
"resolved": "https://registry.npmjs.org/@sentry/browser-utils/-/browser-utils-10.62.0.tgz",
"integrity": "sha512-mS9HVVuWIdye9o0xUGFmzNOBqktF4n5kugrF8NCOYYDrr5ZV8Cx7BlquHQn5UpCeViVhZtcDlEm4iOK7++Px7A==",
"license": "MIT",
"dependencies": {
"@sentry/core": "10.62.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/bundler-plugin-core": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/@sentry/bundler-plugin-core/-/bundler-plugin-core-5.3.0.tgz",
"integrity": "sha512-L5T60sWdAI3qWwdg3Ptwek/0TY59PERrxyqp4XMUkroayQvGd9r5dIW9Q1kSeXX9iJ442nXbFZKAOyCKV4Z13Q==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.18.5",
"@sentry/babel-plugin-component-annotate": "5.3.0",
"@sentry/cli": "^2.58.5",
"dotenv": "^16.3.1",
"find-up": "^5.0.0",
"glob": "^13.0.6",
"magic-string": "~0.30.8"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@sentry/bundler-plugin-core/node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/@sentry/bundler-plugin-core/node_modules/brace-expansion": {
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
"integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/@sentry/bundler-plugin-core/node_modules/dotenv": {
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/@sentry/bundler-plugin-core/node_modules/glob": {
"version": "13.0.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
"integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
"license": "BlueOak-1.0.0",
"dependencies": {
"minimatch": "^10.2.2",
"minipass": "^7.1.3",
"path-scurry": "^2.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@sentry/bundler-plugin-core/node_modules/lru-cache": {
"version": "11.5.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz",
"integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==",
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/@sentry/bundler-plugin-core/node_modules/minimatch": {
"version": "10.2.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^5.0.5"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@sentry/bundler-plugin-core/node_modules/path-scurry": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
"integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^11.0.0",
"minipass": "^7.1.2"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@sentry/cli": {
"version": "2.58.6",
"resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.58.6.tgz",
"integrity": "sha512-baBcNPLLfUi9WuL+Tpri9BFaAdvugZIKelC5X0tt0Zdy+K0K+PCVSrnNmwMWU/HyaF/SEv6b6UHnXIdqanBlcg==",
"hasInstallScript": true,
"license": "FSL-1.1-MIT",
"dependencies": {
"https-proxy-agent": "^5.0.0",
"node-fetch": "^2.6.7",
"progress": "^2.0.3",
"proxy-from-env": "^1.1.0",
"which": "^2.0.2"
},
"bin": {
"sentry-cli": "bin/sentry-cli"
},
"engines": {
"node": ">= 10"
},
"optionalDependencies": {
"@sentry/cli-darwin": "2.58.6",
"@sentry/cli-linux-arm": "2.58.6",
"@sentry/cli-linux-arm64": "2.58.6",
"@sentry/cli-linux-i686": "2.58.6",
"@sentry/cli-linux-x64": "2.58.6",
"@sentry/cli-win32-arm64": "2.58.6",
"@sentry/cli-win32-i686": "2.58.6",
"@sentry/cli-win32-x64": "2.58.6"
}
},
"node_modules/@sentry/cli-darwin": {
"version": "2.58.6",
"resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.58.6.tgz",
"integrity": "sha512-udAVvcyfNa0R+95GvPz/+43/N3TC0TYKdkQ7D7jhPSzbcMc7l2fxRNN5yB3UpCA5fWFnW4toeaqwDBhb/Wh3LA==",
"license": "FSL-1.1-MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli-linux-arm": {
"version": "2.58.6",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.58.6.tgz",
"integrity": "sha512-pD0LAt5PcUzAinBwvDqc66x9+2CabHEv486yP0gRjWO7SakbaxmfVq/EXd8VLq/Tzi39LAu422UYK1lpW3MILw==",
"cpu": [
"arm"
],
"license": "FSL-1.1-MIT",
"optional": true,
"os": [
"linux",
"freebsd",
"android"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli-linux-arm64": {
"version": "2.58.6",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.58.6.tgz",
"integrity": "sha512-q8mEcNNmeXMy5i+jWT30TVpH7LcP4HD21CD5XRSPAd/a912HF6EpK0ybf/1USO14WOhoXbAGi9txwaWabSe33g==",
"cpu": [
"arm64"
],
"license": "FSL-1.1-MIT",
"optional": true,
"os": [
"linux",
"freebsd",
"android"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli-linux-i686": {
"version": "2.58.6",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.58.6.tgz",
"integrity": "sha512-q8vNJi1eOV/4vxAFWBsEwLHoSYapaZHIf4j76KJGJXFKTkEbsjCOOsKbwUIBTQQhRgV4DFWh3ryfsPS/que4Kg==",
"cpu": [
"x86",
"ia32"
],
"license": "FSL-1.1-MIT",
"optional": true,
"os": [
"linux",
"freebsd",
"android"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli-linux-x64": {
"version": "2.58.6",
"resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.58.6.tgz",
"integrity": "sha512-DZu956Mhi3ZRjTBe1WdbGV46ldVbA8d2rgp/fh51GsI25zjBHah4wZnPTSzpc+YqxU6pJpg579B/r3jrIK530Q==",
"cpu": [
"x64"
],
"license": "FSL-1.1-MIT",
"optional": true,
"os": [
"linux",
"freebsd",
"android"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli-win32-arm64": {
"version": "2.58.6",
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.58.6.tgz",
"integrity": "sha512-nj0Ff/kmAB73EPDhR8B4O9r+NUHK5GkPCkGWC+kXVemqAJWL5jcJ5KdxG0l/S0z6RoEoltID8/43/B+TaMlT7A==",
"cpu": [
"arm64"
],
"license": "FSL-1.1-MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli-win32-i686": {
"version": "2.58.6",
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.58.6.tgz",
"integrity": "sha512-WNZiDzPbgsEMQWq4avsQ391v/xWKJDIWWWo9GYl+N/w5qcYKkoDW7wQG7T9FasI6ENn68phChTOAPXXxbfAdOg==",
"cpu": [
"x86",
"ia32"
],
"license": "FSL-1.1-MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli-win32-x64": {
"version": "2.58.6",
"resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.58.6.tgz",
"integrity": "sha512-R35WJ17oF4D2eqI1DR2sQQqr0fjRTt5xoP16WrTu91XM2lndRMFsnjh+/GttbxapLCBNlrjzia99MJ0PZHZpgA==",
"cpu": [
"x64"
],
"license": "FSL-1.1-MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@sentry/cli/node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"license": "MIT",
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/@sentry/cli/node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"license": "MIT",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/@sentry/cloudflare": {
"version": "10.62.0",
"resolved": "https://registry.npmjs.org/@sentry/cloudflare/-/cloudflare-10.62.0.tgz",
"integrity": "sha512-oHDpXXiO3XpBO2cHiTRQpSrtQOQrsU9JsO3TZ6ukdd24IUE6Tkc3l7hWdwzKqId3nTWP1Ef0Fr+offsrEGJ6UA==",
"license": "MIT",
"dependencies": {
"@opentelemetry/api": "^1.9.1",
"@sentry/core": "10.62.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@cloudflare/workers-types": "^4.x"
},
"peerDependenciesMeta": {
"@cloudflare/workers-types": {
"optional": true
}
}
},
"node_modules/@sentry/conventions": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@sentry/conventions/-/conventions-0.12.0.tgz",
"integrity": "sha512-z1JQrl/1SLY+8wpzvork6vl+fpsg/oCCxM7HWWhUnI/R+OGNyoIzieQuggX3uUMY7NBtp8UWCQx6FeFazzOF9g==",
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/@sentry/core": {
"version": "10.62.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.62.0.tgz",
"integrity": "sha512-tV69fMg2sS5DUFmQSnS7Jd5qJAp0izxwcsvBVz2ieTM9VMRi99IfOSYW9UYr3p1yfuksk41kefN5PEbeedUE+A==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/feedback": {
"version": "10.62.0",
"resolved": "https://registry.npmjs.org/@sentry/feedback/-/feedback-10.62.0.tgz",
"integrity": "sha512-d0BVjJVny6qpBgGJgWL0fbcoQHjtD3z3R8EK/KzTS3RO92JX5n3A536n5D/rh0gZFgcIwiUzBXegmyPOSQn9ng==",
"license": "MIT",
"dependencies": {
"@sentry/core": "10.62.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/node": {
"version": "10.62.0",
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.62.0.tgz",
"integrity": "sha512-4hoU67bJY0o3irEDMZu2UIztAOsvEqFkLXA7EUKl1LXMA3Ba1Lb32OUVqlsTypiEInSDs/BtM+aAFKojZ3P3Fw==",
"license": "MIT",
"dependencies": {
"@opentelemetry/api": "^1.9.1",
"@opentelemetry/instrumentation": "^0.214.0",
"@opentelemetry/sdk-trace-base": "^2.6.1",
"@opentelemetry/semantic-conventions": "^1.40.0",
"@sentry/core": "10.62.0",
"@sentry/node-core": "10.62.0",
"@sentry/opentelemetry": "10.62.0",
"@sentry/server-utils": "10.62.0",
"import-in-the-middle": "^3.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/node-core": {
"version": "10.62.0",
"resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.62.0.tgz",
"integrity": "sha512-V7rDgbxViiHU0OpcFEDp3l41IFvWTasKHfXw8SQ6yIgtZ8VpFqmz2TR5N7X85iIOmWIvK5HV0yp0eDdsly0+rA==",
"license": "MIT",
"dependencies": {
"@sentry/conventions": "^0.12.0",
"@sentry/core": "10.62.0",
"@sentry/opentelemetry": "10.62.0",
"import-in-the-middle": "^3.0.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/core": "^1.30.1 || ^2.1.0",
"@opentelemetry/exporter-trace-otlp-http": ">=0.57.0 <1",
"@opentelemetry/instrumentation": ">=0.57.1 <1",
"@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0"
},
"peerDependenciesMeta": {
"@opentelemetry/api": {
"optional": true
},
"@opentelemetry/core": {
"optional": true
},
"@opentelemetry/exporter-trace-otlp-http": {
"optional": true
},
"@opentelemetry/instrumentation": {
"optional": true
},
"@opentelemetry/sdk-trace-base": {
"optional": true
}
}
},
"node_modules/@sentry/nuxt": {
"version": "10.62.0",
"resolved": "https://registry.npmjs.org/@sentry/nuxt/-/nuxt-10.62.0.tgz",
"integrity": "sha512-YM9N4mH/uOJP/zr3QmQgCpQFaLzoDRh0/SoMMuNq/EEtzFZLQT6+qd5tYERMAutU4ySHinIaKYC2Gq/hEs5LtA==",
"license": "MIT",
"dependencies": {
"@nuxt/kit": "^3.13.2",
"@sentry/browser": "10.62.0",
"@sentry/cloudflare": "10.62.0",
"@sentry/core": "10.62.0",
"@sentry/node": "10.62.0",
"@sentry/node-core": "10.62.0",
"@sentry/rollup-plugin": "^5.3.0",
"@sentry/vite-plugin": "^5.3.0",
"@sentry/vue": "10.62.0",
"local-pkg": "^1.1.2"
},
"engines": {
"node": ">=18.19.1"
},
"peerDependencies": {
"nitro": "2.x || 3.x",
"nuxt": ">=3.7.0 || 4.x || 5.x"
},
"peerDependenciesMeta": {
"nitro": {
"optional": true
}
}
},
"node_modules/@sentry/opentelemetry": {
"version": "10.62.0",
"resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.62.0.tgz",
"integrity": "sha512-nFwBgtjfwgY8P5lAuQFWfAsQW1MXxuQ6kR/HtBs+A6julqwGGS2QnQ65OCWMzz6IqDEL/pRgT1405/gU+OXU3A==",
"license": "MIT",
"dependencies": {
"@sentry/conventions": "^0.12.0",
"@sentry/core": "10.62.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/core": "^1.30.1 || ^2.1.0",
"@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0"
}
},
"node_modules/@sentry/replay": {
"version": "10.62.0",
"resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-10.62.0.tgz",
"integrity": "sha512-rWp4hBhZOmdQhisxcKzAwTGiRk/LvWnNaElWe7nbRhjsM/usp2095yfjq4iJ47v9MtO7xxY6eUz++fLBycqXKg==",
"license": "MIT",
"dependencies": {
"@sentry/browser-utils": "10.62.0",
"@sentry/core": "10.62.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/replay-canvas": {
"version": "10.62.0",
"resolved": "https://registry.npmjs.org/@sentry/replay-canvas/-/replay-canvas-10.62.0.tgz",
"integrity": "sha512-CzPAxmpe5US/ABGA1TzpjFKOFZN5uqlzrRh/uM9/daVuzLVKIAQ0XRNxo/PPEXvlDm/PoMdI5L0qIODuIKnyyw==",
"license": "MIT",
"dependencies": {
"@sentry/core": "10.62.0",
"@sentry/replay": "10.62.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/rollup-plugin": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/@sentry/rollup-plugin/-/rollup-plugin-5.3.0.tgz",
"integrity": "sha512-hgPGPYdQJ/G1cGYOxAb7d4z3V+/k/E5/P/5TFPEEBLuIbFFk+JG0CISUDJdzXJjO382Lb99PBJuXGbueBmO79w==",
"license": "MIT",
"dependencies": {
"@sentry/bundler-plugin-core": "5.3.0",
"magic-string": "~0.30.8"
},
"engines": {
"node": ">= 18"
},
"peerDependencies": {
"rollup": ">=3.2.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@sentry/server-utils": {
"version": "10.62.0",
"resolved": "https://registry.npmjs.org/@sentry/server-utils/-/server-utils-10.62.0.tgz",
"integrity": "sha512-S5szsj6kKBhxw97b2HA98fYp/PpWXvSizlisEzb2rnL4IH6RAJ8wP05/fnth8pSywTH+gtUu+i6Wn8e8rX5HvA==",
"license": "MIT",
"dependencies": {
"@apm-js-collab/code-transformer": "^0.15.0",
"@apm-js-collab/code-transformer-bundler-plugins": "^0.5.0",
"@apm-js-collab/tracing-hooks": "^0.10.0",
"@sentry/conventions": "^0.12.0",
"@sentry/core": "10.62.0",
"magic-string": "~0.30.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/vite-plugin": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/@sentry/vite-plugin/-/vite-plugin-5.3.0.tgz",
"integrity": "sha512-qcoSzo4n2MulVQ70UUPLq6dTleb2a2HwL2wuwvAgWhPChrYTuk6A6mDg6aQb9fairPAwFPiU9PzOANpoDJcz1A==",
"license": "MIT",
"dependencies": {
"@sentry/bundler-plugin-core": "5.3.0",
"@sentry/rollup-plugin": "5.3.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@sentry/vue": {
"version": "10.62.0",
"resolved": "https://registry.npmjs.org/@sentry/vue/-/vue-10.62.0.tgz",
"integrity": "sha512-aK3E302Zx/g1dqtUU30Q0jblvCW8MsVXuzwnxM4JSgO47o0jW74zaFh1K3Ym2uQWhLvP1rV2D49BYwCMUc4ovQ==",
"license": "MIT",
"dependencies": {
"@sentry/browser": "10.62.0",
"@sentry/core": "10.62.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@tanstack/vue-router": "^1.64.0",
"pinia": "2.x || 3.x",
"vue": "2.x || 3.x"
},
"peerDependenciesMeta": {
"@tanstack/vue-router": {
"optional": true
},
"pinia": {
"optional": true
}
}
},
"node_modules/@sindresorhus/is": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.1.0.tgz",
@@ -5451,6 +6203,15 @@
"url": "https://github.com/sponsors/sxzz"
}
},
"node_modules/astring": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz",
"integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==",
"license": "MIT",
"bin": {
"astring": "bin/astring"
}
},
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
@@ -5958,6 +6719,12 @@
"consola": "^3.2.3"
}
},
"node_modules/cjs-module-lexer": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz",
"integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==",
"license": "MIT"
},
"node_modules/clean-regexp": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz",
@@ -7481,10 +8248,9 @@
}
},
"node_modules/esquery": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
"integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
"devOptional": true,
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
"integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
"license": "BSD-3-Clause",
"dependencies": {
"estraverse": "^5.1.0"
@@ -7510,7 +8276,6 @@
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"devOptional": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=4.0"
@@ -7732,7 +8497,6 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"locate-path": "^6.0.0",
@@ -8385,6 +9149,21 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/import-in-the-middle": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-3.2.0.tgz",
"integrity": "sha512-vR2B6HKIhaBjcZr2bLpFiJ1VbzOlRQ7aby4/gw5WPIzToLjqpfWw3VJ4sk1uDchoOODEirvO2jyrSPtUSL5CrQ==",
"license": "Apache-2.0",
"dependencies": {
"acorn": "^8.15.0",
"acorn-import-attributes": "^1.9.5",
"cjs-module-lexer": "^2.2.0",
"module-details-from-path": "^1.0.4"
},
"engines": {
"node": ">=18"
}
},
"node_modules/impound": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/impound/-/impound-1.0.0.tgz",
@@ -9514,7 +10293,6 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"p-locate": "^5.0.0"
@@ -9670,6 +10448,15 @@
"node": ">= 8"
}
},
"node_modules/meriyah": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/meriyah/-/meriyah-6.1.4.tgz",
"integrity": "sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ==",
"license": "ISC",
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@@ -9768,10 +10555,10 @@
}
},
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"license": "ISC",
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=16 || 14 >=14.17"
}
@@ -9829,6 +10616,12 @@
"integrity": "sha512-aF7yRQr/Q0O2/4pIXm6PZ5G+jAd7QS4Yu8m+WEeEHGnbo+7mE36CbLSDQiXYV8bVL3NfmdeqPJct0tUlnjVSnA==",
"license": "MIT"
},
"node_modules/module-details-from-path": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz",
"integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==",
"license": "MIT"
},
"node_modules/mrmime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
@@ -10185,6 +10978,7 @@
"resolved": "https://registry.npmjs.org/nuxt/-/nuxt-4.1.2.tgz",
"integrity": "sha512-g5mwszCZT4ZeGJm83nxoZvtvZoAEaY65VDdn7p7UgznePbRaEJJ1KS1OIld4FPVkoDZ8TEVuDNqI9gUn12Exvg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@nuxt/cli": "^3.28.0",
"@nuxt/devalue": "^2.0.2",
@@ -10618,7 +11412,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"yocto-queue": "^0.1.0"
@@ -10634,7 +11427,6 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"p-limit": "^3.0.2"
@@ -10729,7 +11521,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">=8"
@@ -11694,6 +12485,15 @@
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"license": "MIT"
},
"node_modules/progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/prompts": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
@@ -11720,6 +12520,12 @@
"integrity": "sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==",
"license": "MIT"
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -12011,6 +12817,19 @@
"node": ">=0.10.0"
}
},
"node_modules/require-in-the-middle": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz",
"integrity": "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.5",
"module-details-from-path": "^1.0.3"
},
"engines": {
"node": ">=9.3.0 || >=8.10.0 <9.0.0"
}
},
"node_modules/resolve": {
"version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
@@ -12296,6 +13115,12 @@
"integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
"license": "MIT"
},
"node_modules/semifies": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/semifies/-/semifies-1.0.0.tgz",
"integrity": "sha512-xXR3KGeoxTNWPD4aBvL5NUpMTT7WMANr3EWnaS190QVkY52lqqcVRD7Q05UVbBhiWDGWMlJEUam9m7uFFGVScw==",
"license": "Apache-2.0"
},
"node_modules/semver": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
@@ -14604,7 +15429,6 @@
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">=10"
+1
View File
@@ -19,6 +19,7 @@
},
"dependencies": {
"@nuxtjs/tailwindcss": "^6.14.0",
"@sentry/nuxt": "^10.61.0",
"@tailwindcss/vite": "^4.1.11",
"daisyui": "^5.0.48",
"nuxt": "^4.0.1",
+18
View File
@@ -0,0 +1,18 @@
import * as Sentry from '@sentry/nuxt'
// Init Sentry côté client (SPA). Le DSN provient du build prod (NUXT_PUBLIC_SENTRY_DSN).
// Si le DSN est vide (dev), Sentry.init est un no-op : rien n'est envoyé.
const config = useRuntimeConfig()
const dsn = config.public.sentry?.dsn
if (dsn) {
Sentry.init({
dsn,
environment: config.public.sentry?.environment,
// Pas d'APM/tracing (hors périmètre ticket #146) : on ne remonte que les erreurs.
tracesSampleRate: 0,
// Pas de session replay (volume).
replaysSessionSampleRate: 0,
replaysOnErrorSampleRate: 0,
})
}
+4
View File
@@ -8,3 +8,7 @@ DATABASE_URL="postgresql://inventory_user:password@host.docker.internal:5432/inv
# CORS
CORS_ALLOW_ORIGIN='^https?://inventory\.malio-dev\.fr$'
# Sentry / GlitchTip — error tracking backend (projet "inventory-api").
# Runtime, prod only. Vide/absent => SDK inerte (rien envoyé).
# SENTRY_DSN=http://<clé>@<host-ou-IP>:<port>/<id-projet>
+23 -2
View File
@@ -31,21 +31,42 @@ RUN npm ci
COPY frontend/ ./
COPY config/version.yaml /app/config/version.yaml
# Error tracking → GlitchTip (build-time). Vides par défaut => module Sentry inerte
# et aucun upload de source maps. Fournis par la CI via --build-arg (secrets Gitea).
# Passés en préfixe inline du RUN (pas en ENV) pour ne pas persister le token dans
# une couche d'image.
ARG NUXT_PUBLIC_SENTRY_DSN=""
ARG SENTRY_URL=""
ARG SENTRY_ORG=""
ARG SENTRY_PROJECT=""
ARG SENTRY_AUTH_TOKEN=""
ENV CI=1 \
NUXT_TELEMETRY_DISABLED=1 \
NUXT_PUBLIC_API_BASE_URL=/api \
NUXT_PUBLIC_APP_BASE=/
RUN npm run generate
RUN NUXT_PUBLIC_SENTRY_DSN="$NUXT_PUBLIC_SENTRY_DSN" \
SENTRY_URL="$SENTRY_URL" \
SENTRY_ORG="$SENTRY_ORG" \
SENTRY_PROJECT="$SENTRY_PROJECT" \
SENTRY_AUTH_TOKEN="$SENTRY_AUTH_TOKEN" \
npm run generate
# --- Stage 3: Production image ---
FROM php:8.4-fpm AS production
RUN apt-get update && apt-get install -y \
libicu-dev libpq-dev libpng-dev libzip-dev libxml2-dev \
nginx supervisor qpdf \
nginx supervisor qpdf ca-certificates \
&& docker-php-ext-install -j$(nproc) intl pdo_pgsql zip gd opcache \
&& rm -rf /var/lib/apt/lists/*
# CA racine interne MALIO (auto-signée) — permet au SDK Sentry/HttpClient de
# joindre les services HTTPS internes (ex. GlitchTip sur logs.malio-dev.fr).
COPY infra/prod/malio-dev-root-ca.crt /usr/local/share/ca-certificates/malio-dev-root-ca.crt
RUN update-ca-certificates
# PHP production config
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
+31
View File
@@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFZzCCA0+gAwIBAgIUOiZigxwgIgtLipnLnu4eSgItc5MwDQYJKoZIhvcNAQEL
BQAwQzELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU1BTElPLURFVjEgMB4GA1UEAwwX
TUFMSU8tREVWIExvY2FsIFJvb3QgQ0EwHhcNMjYwNjI1MTYxMjIwWhcNMzYwNjIy
MTYxMjIwWjBDMQswCQYDVQQGEwJGUjESMBAGA1UECgwJTUFMSU8tREVWMSAwHgYD
VQQDDBdNQUxJTy1ERVYgTG9jYWwgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQAD
ggIPADCCAgoCggIBALqHXVWEae9aKtveLfSpxYy9RS0Aslw2Ls9+LWI33lpMRs02
QssE9wquf3WGjz8NnHUWl5RM0QHC0DOCCddcbnRBciDRJeTaU43IGdNg+TSY+7aM
3t/jysZrpc/eu/udlIs7npCPaOGnRiuGN68Fkf9Q70FtmaASpusUe7J3jKDinznr
R2hARplO4OF01tFauu039A4yudLrZTUFTldicuZ6a5U3NhajgfNZA+pyJqvL3tLT
lXG3KupPD9BsbWe4zSM96CmyHM22QNlcL+M5XG5+EtDtM07tkDcyxFOsREjQHvSQ
NH+7h6G/QBHHKkYJhdyiuvpj6b5tEJBM2PVgy1T2JX5TuOBOLx6HvHLbNjUY/JI5
0sIjnHbeybQCOfnKNAwidtnqjAfVg+XJ9UZCiGJOeRJOdN5isvvqEKydsX4ouCTj
89kwBbfCJeCS6BiadvNFUwnM0PksV0ovnOiUEEAPHRiP74jZ3IvH95BEwiZzyLpy
tXiJMW7cJMaqlT3jNwq3P00irfrpJNy4S1Mg2cBQh5ucv+PcMBfQT8YiarzlTQJo
saksh/2C43WH+qIFAL2aeD+rKReVBZcGa1XOBI8FUJTu3rLd37+iS4N2BUKq4fWo
FttuX5NOfeU3BRDLlCJ2AXau7o0czVy896R9iZTfBJC95QWD07PdHgoctuexAgMB
AAGjUzBRMB0GA1UdDgQWBBRNU0WsMg/pqo5XF/WXx78GrAzD5TAfBgNVHSMEGDAW
gBRNU0WsMg/pqo5XF/WXx78GrAzD5TAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
DQEBCwUAA4ICAQBFXsuT7Rm2oJBlWT/RsJtmWr95NoFLHovVDycgM8Vjm+E8hv/m
AcSjPjZDmXQLOrN31T/XUAs0nURHxSFgVzdIKpq2gOlGgHkZRMAW/iTON9Cqjn81
Arjp5fjAJyFkoCiT3eTOElpteF4NhL8xMFaOg1Y2CEfOYO9OZR7Z38HdB6IArVwr
W3Dxq3DPtarCeo1k8SHJmJzUduYCltV8urB43gIiI2Hqd7aAlpkTfDhruKxxr7sJ
3/TpemJDCN9m8XMv2QvxqpMwH6EXg/7oqit5k0MvD445f3xt9vZydmV/x6F7u/A/
gJitN+ixA4AKv7Lw210vaupiChqdY+78TXgLoPJ2/l2QPWG/R7Fb4yNZ2rEd6lyt
KLPxHDcdZetFnyqyaoB2SNtLx9hNUE5G3udU6DkNhDfQlDhqEG4f7GAInOu/cMWE
2uiIUEjcGSLM+XrrTFRc1tdXy6hnu+sw5ckvhwJ+kjah/pVGz21/y5a0p42AUznI
iN7HBV8YaSkeJLvBPnfakUAat1R98e0l72DucHe8RF44NmZCywpaUBsTpNy+bO2f
atqp4/ZEGJJlJ38rLv9bAuwr6d8x6T+m0oHknqtJHcWfO0kr4l3Lxsd8mRpGgmBe
zOjqjrat4vSc04Rqic4UV2IEoWCiSS/TSiBx8JAB6Ck0+YR9dUgXVQsFFg==
-----END CERTIFICATE-----
+9
View File
@@ -109,6 +109,15 @@
"bin/phpunit"
]
},
"sentry/sentry-symfony": {
"version": "5.10",
"recipe": {
"repo": "github.com/symfony/recipes-contrib",
"branch": "main",
"version": "5.0",
"ref": "aac2bc5220e9ab5b9e3838a7a4da90e7f74e6148"
}
},
"symfony/console": {
"version": "8.0",
"recipe": {