# Lesstime Application de gestion de projet. Monorepo Symfony 8 (API Platform 4) + Nuxt 4. > **WIP — Intégration Mail (branche `feat/mail-integration`)** : client mail OVH IMAP. Avant de toucher au mail, lire `docs/mail-integration.md` (section « Statut & reprise » = bugs déjà corrigés, points en suspens, commandes). Code : `src/Mail/`, `src/Service/MailSyncService.php`, `src/Controller/Mail/`, `frontend/{services,stores,components}/mail*`. ## Stack - **Backend** : PHP 8.4, Symfony 8.0, API Platform 4, Doctrine ORM, PostgreSQL 16 - **Frontend** : Nuxt 4 (SSR off / SPA), Vue 3, Pinia, Tailwind CSS, @malio/layer-ui, nuxt-toast, @nuxtjs/i18n, @nuxt/icon - **Auth** : JWT HTTP-only cookie (lexik/jwt-authentication-bundle), login à `/login_check`, cookie `BEARER` - **Docker** : PHP-FPM + Node 24, Nginx (port 8082), PostgreSQL (port 5435) ## Structure > Le détail (entités, providers, services, composants…) se découvre dans le code. Carte d'orientation : ``` src/Entity/ # Entités Doctrine (User, Client, Project, Task + métadonnées Task*, TimeEntry, Notification, *Configuration…) src/ApiResource/ # Ressources API Platform découplées des entités src/State/ # Providers & Processors API Platform (Me, ActiveTimeEntry, TaskNumber, Notification, Gitea*, Zimbra*, RecurrenceHandler…) src/Service/ # Services métier (NotificationService, CalDavService, RecurrenceCalculator) src/Controller/ # Controllers custom (notifications, avatar, download document) src/Mcp/Tool/ # MCP tools par domaine (Project/, Task/, TaskMeta/, TimeEntry/, Reference/) src/Security/ # ApiTokenAuthenticator (MCP HTTP) src/Command/ src/Repository/ src/DataFixtures/ config/ # security, api_platform, lexik_jwt, nelmio_cors, doctrine — config/jwt/ = clés migrations/ docs/plans/ docs/superpowers/ frontend/pages/ # index, login, my-tasks, profile, projects/[id]/*, time-tracking, admin frontend/components/ # Sous-dossiers ui/ client/ project/ task/ user/ admin/ time-tracking/ notification/ frontend/composables/# useApi, useAppVersion, useNotifications, useAvatarService frontend/stores/ # Pinia : auth, ui, timer frontend/services/ # 1 service par ressource API (+ services/dto/ pour les types) frontend/i18n/locales/ # Traductions (langDir résolu depuis i18n/) ``` ## Commandes ```bash make start # Démarrer les containers make stop # Arrêter les containers make restart # Redémarrer les containers make install # Install complet (composer, migrations, fixtures, build Nuxt) make reset # Tout supprimer et réinstaller (supprime la BDD) make dev-nuxt # Dev server Nuxt (hot reload, port 3002) make shell # Shell dans le container PHP make shell-root # Shell root dans le container PHP make cache-clear # Vider le cache Symfony make migration-migrate # Lancer les migrations make fixtures # Charger les fixtures make db-reset # Reset BDD + migrations + fixtures make test # PHPUnit make php-cs-fixer-allow-risky # Fix code style PHP make logs-dev # Tail logs Symfony ``` ## Conventions ### Commits Format : `() : ` (espace avant et après `:`) Types autorisés (minuscules) : `build`, `chore`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, `test` Exemples : `feat : add login page`, `fix(auth) : prevent null token crash` ### Tags & Versioning - La version de l'app est dans `config/version.yaml` (paramètre `app.version`) - À chaque création de tag, **toujours** mettre à jour `config/version.yaml` avec la même version - Faire un commit séparé de bump : `chore : bump version to v` - Puis créer le tag et pusher : `git tag v && git push origin develop --tags` ### Backend - Toujours `declare(strict_types=1)` en haut des fichiers PHP - API Platform : utiliser ApiResource, Providers (`src/State/`), Processors — pas de controllers - Routes API préfixées `/api` (via `config/routes/api_platform.yaml`) - Le login (`/login_check`) est hors prefix `/api`, nginx réécrit `REQUEST_URI` vers `/login_check` - PHP CS Fixer : règles Symfony + PSR-12 + strict types - Rôles : `ROLE_ADMIN`, `ROLE_USER` — hiérarchie dans `security.yaml` - `User::getRoles()` ajoute toujours `ROLE_USER` - PostgreSQL : `LIKE` sur colonne JSON ne marche pas → utiliser `roles::text LIKE` via native SQL - Controllers custom sous `/api/` : ajouter `priority: 1` sur `#[Route]` pour éviter le conflit avec API Platform `{id}` - Serialization : pour embarquer une relation (pas IRI), ajouter le groupe du parent aux propriétés de l'entité cible - Upload fichiers : utiliser `$file->getMimeType()` (pas `getClientMimeType()`) pour valider côté serveur — nécessite `symfony/mime` - Endpoints ouverts à tout utilisateur authentifié : utiliser `#[IsGranted('IS_AUTHENTICATED_FULLY')]` au lieu d'un rôle spécifique ### Frontend - TypeScript strict - Composable `useApi()` pour tous les appels API (gère cookies, erreurs, toasts, i18n) - Stores Pinia : `useAuthStore` (auth), `useUiStore` (ui), `useTimerStore` (timer) - Middleware global `auth.global.ts` protège les routes - Traductions dans `frontend/i18n/locales/` (le module résout `langDir` depuis `i18n/`) - 4 espaces d'indentation - MalioSelect : options `{ label: string, value: string | number | null }` — accepte les valeurs **string** (enums string OK, ex `category`/`StatusCategory`), pas seulement `number` (vérifié dans la source `Select.vue` : `modelValue: string | number | null`). L'option vide `null` n'est ajoutée que si `empty-option-label` est passé (ne pas le passer pour un champ requis). Largeur via `group-class` (pas de prop `minWidth`/`min-width`). ⚠️ Le `COMPONENTS.md` de la lib est inexact sur ce composant (il indique une clé `text` et une prop `minWidth` inexistantes) : la clé d'affichage réelle est `label`. Ne jamais modifier la lib `malio-layer-ui` depuis ce projet. ### Composants UI La librairie `@malio/layer-ui` fournit les composants de formulaire et d'action. La documentation complète des props, events et exemples d'utilisation se trouve dans `frontend/node_modules/@malio/layer-ui/COMPONENTS.md`. Toujours s'y référer avant d'utiliser un composant Malio. ### MCP Server - 60 tools MCP exposant projets, tâches, métadonnées, time tracking, récurrences, documents et absences - Transport STDIO (local) : `docker exec -i php-lesstime-fpm php bin/console mcp:server` - Transport HTTP (réseau) : `POST /_mcp` avec header `Authorization: Bearer ` - Auth HTTP : `ApiTokenAuthenticator` vérifie le champ `apiToken` de l'entité `User` - Générer un token : `php bin/console app:generate-api-token ` - Config : `config/packages/mcp.yaml`, firewall dans `config/packages/security.yaml` - Attribut `#[McpTool]` doit être sur la **classe** (pas la méthode `__invoke`) pour la discovery SDK ### Nginx - `/_mcp` → Symfony (MCP HTTP transport) - `/api/*` → Symfony (via try_files + index.php) - `/api/login_check` → location exact match, fastcgi direct avec REQUEST_URI réécrit en `/login_check` - `/` → SPA frontend (`frontend/dist/`) ## Docker - Container PHP : `php-lesstime-fpm` - Container Nginx : `nginx-lesstime` - Container DB : PostgreSQL sur port **5435** (interne et externe) - Config Docker : `infra/dev/.env.docker` (override local : `infra/dev/.env.docker.local`) - Après modif nginx : `docker restart nginx-lesstime` ## Fixtures - User admin : `admin` / `admin` (ROLE_ADMIN) - Users internes : `alice` / `alice`, `bob` / `bob`, `charlie` / `charlie` (ROLE_USER) - API token admin (dev) : `dev-mcp-token-for-testing-only-do-not-use-in-production` - ZimbraConfiguration : serverUrl `https://mail.ovh.com`, username `lesstime@ovh.fr`, enabled false - TaskRecurrence (hebdomadaire lun/mer/ven) attachée à la tâche "Réunion de suivi hebdomadaire" (SIRH) ## Delegation Codex Pour les taches mecaniques (tests, boilerplate, renommages, refacto repetitif), delegue a Codex via le plugin `codex`. Garde Claude pour la reflexion, l'architecture et la verification. - **Codex** = junior dev rapide et pas cher (executions mecaniques) - **Claude** = senior dev qui verifie et reflechit (design, review, decisions) C'est le meilleur ratio qualite/credits.