Compare commits

..

5 Commits

Author SHA1 Message Date
gitea-actions
8f2a688740 chore: bump version to v0.4.6
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
Build & Push Docker Image / build (push) Successful in 48s
2026-05-21 09:19:01 +00:00
Matthieu
6491943930 docs : ajoute la section Messagerie au centre d'aide + maj admin/intégrations (mail OVH)
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 11:18:42 +02:00
gitea-actions
a9f05fd819 chore: bump version to v0.4.5
All checks were successful
Build & Push Docker Image / build (push) Successful in 53s
Auto Tag Develop / tag (push) Successful in 6s
2026-05-21 08:57:40 +00:00
Matthieu
925be5d181 fix(ui) : sélecteur de statut Malio dans le drawer de création de workflow
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
Remplace le <select> natif du navigateur par MalioSelect (catégorie de statut).
MalioSelect accepte les valeurs string (enum StatusCategory), contrairement à ce
qu'indiquait la note CLAUDE.md — note corrigée en conséquence.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 10:53:19 +02:00
Matthieu
5da165f739 docs : corrige le déploiement prod (Docker) et documente les variables d'env mail
- README : section Variables d'environnement (ENCRYPTION_KEY, LOCK_DSN) + section Déploiement passée au flow Docker (deploy.sh)
- mail-cron-setup : sépare dev (make, php-lesstime-fpm) et prod (lesstime-app, docker compose exec), cron prod réel
- infra/prod/.env.example : ajoute ENCRYPTION_KEY et LOCK_DSN (manquaient, requis pour la sync mail)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 10:48:55 +02:00
10 changed files with 209 additions and 80 deletions

View File

@@ -101,7 +101,7 @@ Exemples : `feat : add login page`, `fix(auth) : prevent null token crash`
- 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 }`supporte les valeurs `string` (donc les enums string comme `StatusCategory`), pas seulement `number`. Vérifié dans la source `@malio/layer-ui` (`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)
- 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.
- Portal client : pages sous `/portal/`, layout `portal.vue`, middleware redirige `ROLE_CLIENT` (sans `ROLE_ADMIN`) vers `/portal`
- Users admin+client : ne pas bloquer — vérifier `ROLE_CLIENT && !ROLE_ADMIN` pour les restrictions

View File

@@ -45,6 +45,10 @@ make install
L'application est accessible sur **http://localhost:8082**.
Les valeurs par défaut du `.env` committé suffisent pour démarrer en local. Pour la prod
(et pour activer la messagerie), surcharger les variables sensibles dans `.env.local`
voir « Variables d'environnement » ci-dessous.
### Comptes de test (fixtures)
| Utilisateur | Mot de passe | Rôle | Détails |
@@ -56,6 +60,25 @@ L'application est accessible sur **http://localhost:8082**.
| `client-liot` | `client` | ROLE_CLIENT | Client LIOT (projet SIRH) |
| `client-acme` | `client` | ROLE_CLIENT | Client ACME (projet CRM) |
## Variables d'environnement
Les variables sont définies dans `.env` (committé, valeurs par défaut pour le dev) et
peuvent être surchargées dans `.env.local` (jamais committé). En prod, elles vont dans le
`.env` du serveur (`/var/www/lesstime/.env`, voir `infra/prod/.env.example`).
| Variable | Rôle | Défaut dev | À fixer en prod |
|----------|------|-----------|-----------------|
| `APP_SECRET` | Secret Symfony | placeholder | ✅ (hex 32) |
| `JWT_PASSPHRASE` | Passphrase des clés JWT | placeholder | ✅ |
| `DATABASE_URL` | Connexion PostgreSQL | container `db` | ✅ (`host.docker.internal`) |
| `CORS_ALLOW_ORIGIN` | Origines CORS autorisées | localhost | ✅ (domaine prod) |
| **`ENCRYPTION_KEY`** | **Clé hex 32 bytes chiffrant les credentials IMAP/SMTP (feature mail)** | placeholder | ✅ — doit rester **stable**, sinon les credentials mail stockés deviennent illisibles |
| **`LOCK_DSN`** | **Store de verrous Symfony pour la sync mail (anti-chevauchement)** | `flock` | `flock` suffit |
> **Messagerie** : `ENCRYPTION_KEY` et `LOCK_DSN` sont introduites par l'intégration mail.
> Détails de config et cron de synchronisation : `docs/mail-integration.md` et `docs/mail-cron-setup.md`.
> Générer une clé : `php -r "echo bin2hex(random_bytes(32));"`.
## Commandes
### Docker
@@ -218,13 +241,19 @@ docker exec -u www-data php-lesstime-fpm php bin/console app:generate-api-token
## Déploiement
1. Déployer le code sur le serveur
2. `composer install --no-dev --optimize-autoloader`
3. `php bin/console doctrine:migrations:migrate --no-interaction`
4. `php bin/console cache:clear --env=prod`
5. `cd frontend && npm install && npm run build:dist`
6. `docker restart nginx-lesstime`
7. Ouvrir le port 8082 sur le firewall (LAN uniquement)
La prod tourne en **Docker** : l'image est buildée par la CI Gitea sur push de tag `v*`
(`gitea.malio.fr/malio-dev/lesstime:<tag>`), puis déployée par le script `deploy.sh` sur
le serveur (dossier `/var/www/lesstime`, container `lesstime-app`).
```bash
# Sur le serveur, depuis /var/www/lesstime
sudo ./deploy.sh # déploie la dernière image (latest)
sudo ./deploy.sh v0.4.2 # déploie une version précise
```
Le script active la maintenance, pull l'image, redémarre le container, lance les migrations
et vide le cache. Guide complet (première installation, BDD, Nginx, JWT, rollback) :
**`doc/deployment-docker.md`**.
## Licence

View File

@@ -1,2 +1,2 @@
parameters:
app.version: '0.4.4'
app.version: '0.4.6'

View File

@@ -3,44 +3,24 @@
## Vue d'ensemble
La synchronisation IMAP est déclenchée par un cron OS toutes les 10 minutes.
Elle appelle la commande Symfony `app:mail:sync` qui s'exécute dans le container PHP.
Elle appelle la commande Symfony `app:mail:sync` qui s'exécute **dans le container PHP**.
Un Symfony Lock (`mail.sync`, TTL 10 min, store `flock` via `LOCK_DSN=flock`) empêche
les runs de se chevaucher si une sync prend plus de 10 min.
> **Dev vs prod** — en dev le container s'appelle `php-lesstime-fpm` et on passe par `make`.
> En **production** le container s'appelle `lesstime-app` (service `app` du `docker-compose.yml`
> dans `/var/www/lesstime`), il n'y a **pas de `make`** : tout passe par `docker compose` / `docker exec`.
## Prérequis
- Container `php-lesstime-fpm` démarré (`make start`)
- `MailConfiguration.enabled = true` (configurable depuis l'admin — Phase 7)
- `ENCRYPTION_KEY` défini dans `infra/dev/.env.docker.local` (ou production env)
## Installation du cron
Sur la **machine hôte** (pas dans le container) :
```bash
crontab -e
```
Ajouter la ligne suivante (adapter le chemin) :
```cron
*/10 * * * * cd /home/r-dev/malio-dev/Lesstime && make mail-sync >> /var/log/lesstime-mail-sync.log 2>&1
```
Ou directement via `docker exec` (sans dépendance à `make`) :
```cron
*/10 * * * * docker exec php-lesstime-fpm php bin/console app:mail:sync >> /var/log/lesstime-mail-sync.log 2>&1
```
### Avec un utilisateur système dédié
Si le cron est configuré pour un utilisateur système spécifique (ex: `www-data` ou `deploy`) :
```bash
sudo crontab -u deploy -e
```
- `MailConfiguration.enabled = true` (configurable depuis l'admin — onglet « Mail »)
- `ENCRYPTION_KEY` (clé hex 32 bytes) défini dans l'environnement :
- **dev** : `infra/dev/.env.docker.local`
- **prod** : `/var/www/lesstime/.env`
- Container démarré :
- **dev** : `make start` (container `php-lesstime-fpm`)
- **prod** : déployé via `sudo ./deploy.sh` (container `lesstime-app`)
## Variables d'environnement nécessaires
@@ -49,63 +29,100 @@ sudo crontab -u deploy -e
| `ENCRYPTION_KEY` | Clé hex 32 bytes pour déchiffrer le password IMAP | `$(php -r "echo bin2hex(random_bytes(32));")` |
| `LOCK_DSN` | DSN du store de verrous Symfony | `flock` (défaut, fichier local) |
La clé doit être la même que celle utilisée pour chiffrer le password lors de la configuration.
La clé `ENCRYPTION_KEY` doit être **identique** à celle utilisée pour chiffrer le password
lors de la configuration depuis l'admin. Si elle change, les credentials stockés deviennent illisibles.
## Checklist setup production
---
1. [ ] Définir `ENCRYPTION_KEY` dans les variables d'environnement production
2. [ ] Créer le compte mail dédié (ex: `lesstime@votre-domaine.fr`) chez OVH
3. [ ] Accéder à `/admin` → onglet "Mail" → renseigner les credentials IMAP/SMTP
4. [ ] Cliquer "Tester la connexion" → vérifier le succès
5. [ ] Cocher "Activer la synchronisation" → Enregistrer
6. [ ] Installer le cron OS (voir section "Installation du cron")
7. [ ] Vérifier les logs après la première sync : `make logs-dev` (chercher `mail.sync`)
## Dev
## Commandes utiles
### Lancer une sync à la main
```bash
# Sync complète (toutes les boîtes)
make mail-sync
make mail-sync # sync complète (toutes les boîtes)
make mail-sync FOLDER=INBOX # un seul dossier (doit déjà exister en base)
make mail-sync DRYRUN=1 # simulation (dry-run, pas d'écriture BDD)
```
# Sync d'un seul dossier (le dossier doit déjà exister en base)
make mail-sync FOLDER=INBOX
Ou directement dans le container :
# Simulation (dry-run, pas d'écriture BDD)
make mail-sync DRYRUN=1
# Directement dans le container
```bash
docker exec php-lesstime-fpm php bin/console app:mail:sync
docker exec php-lesstime-fpm php bin/console app:mail:sync --folder=INBOX
docker exec php-lesstime-fpm php bin/console app:mail:sync --dry-run
```
## Logs
Les logs Symfony sont dans `var/log/dev.log` (ou `prod.log` en production).
Suivre les logs en temps réel :
### Logs (dev)
```bash
make logs-dev
make logs-dev # tail -f var/log/dev.log
```
Les messages loggés par `MailSyncService` sont préfixés `mail.sync`.
---
## Production
En prod, l'app tourne dans le container `lesstime-app` déployé par `sudo ./deploy.sh`
(dossier `/var/www/lesstime`). La commande s'exécute en tant que `www-data` (uid 33),
comme les migrations lancées par `deploy.sh`.
### Lancer une sync à la main
Depuis `/var/www/lesstime` :
```bash
sudo docker compose exec -T -u www-data app php bin/console app:mail:sync
sudo docker compose exec -T -u www-data app php bin/console app:mail:sync --folder=INBOX
sudo docker compose exec -T -u www-data app php bin/console app:mail:sync --dry-run
```
### Installer le cron
Sur la **machine hôte** (pas dans le container). Comme `docker` requiert `sudo` en prod,
installer le cron sous root :
```bash
sudo crontab -e
```
Ajouter :
```cron
*/10 * * * * cd /var/www/lesstime && docker compose exec -T -u www-data app php bin/console app:mail:sync >> /var/log/lesstime-mail-sync.log 2>&1
```
> Le crontab de root exécute déjà les commandes en root → pas de `sudo` à l'intérieur de la ligne cron.
> La commande est **idempotente** (UIDs uniques en base) : la relancer ne duplique pas les données.
### Logs (prod)
```bash
cd /var/www/lesstime
docker compose logs -f --tail=100 app # logs container
docker compose exec app cat var/log/prod.log # log Symfony (volume lesstime_logs)
```
### Checklist setup production
1. [ ] Définir `ENCRYPTION_KEY` (hex 32 bytes) et `LOCK_DSN=flock` dans `/var/www/lesstime/.env`
2. [ ] Créer le compte mail dédié (ex: `lesstime@votre-domaine.fr`) chez OVH
3. [ ] Accéder à `/admin` → onglet « Mail » → renseigner les credentials IMAP/SMTP
4. [ ] Cliquer « Tester la connexion » → vérifier le succès
5. [ ] Cocher « Activer la synchronisation » → Enregistrer
6. [ ] Lancer une sync manuelle pour valider (commande ci-dessus)
7. [ ] Installer le cron OS (voir « Installer le cron »)
8. [ ] Vérifier les logs après la première sync (`docker compose logs -f app`, chercher `mail.sync`)
---
## Sécurité
- Le password IMAP est **toujours stocké chiffré** (libsodium secretbox)
- Les corps de mails, passwords et pièces jointes ne sont **jamais loggés**
- Le lock `flock` évite les runs parallèles (fichier dans `/tmp/sf.mail.sync.<hash>.lock`)
## Rappels sécurité
- La page `/mail` et tous les endpoints `/api/mail/*` sont refusés aux `ROLE_CLIENT` exclusifs
- Le sidebar "Messagerie" est masqué pour les utilisateurs ROLE_CLIENT sans ROLE_USER
- Le password IMAP est chiffré via libsodium secretbox avant stockage (jamais en clair en base)
- Les corps de mails sont sanitisés via DOMPurify avant affichage (voir `frontend/utils/sanitizeMailHtml.ts`)
- Les pixels tracking distants sont remplacés par un placeholder
- Aucun body mail, password ou contenu de pièce jointe n'est loggé
## Production
En production, préférer un cron système ou un job scheduler (Kubernetes CronJob, ECS Scheduled Task, etc.).
La commande est idempotente : relancer plusieurs fois ne duplique pas les données (UIDs uniques en base).
- Le sidebar « Messagerie » est masqué pour les utilisateurs `ROLE_CLIENT` sans `ROLE_USER`
- Les corps de mails sont sanitisés via DOMPurify avant affichage (`frontend/utils/sanitizeMailHtml.ts`)
- Les pixels de tracking distants sont remplacés par un placeholder

View File

@@ -50,8 +50,7 @@
v-model="s.category"
:options="categoryOptions"
label="Catégorie"
min-width="w-44"
group-class="shrink-0"
group-class="w-48 shrink-0"
/>
<button
type="button"

View File

@@ -64,3 +64,13 @@ L'admin (`/admin`) est divisé en plusieurs onglets, chacun gérant une ressourc
- Configure le calendrier CalDav par défaut
- Test de connexion intégré
- Active la **sync calendrier** sur les tâches planifiées
## Onglet *Mail*
Configure la **boîte mail partagée** (OVH) lue dans la section *Messagerie*.
- **Réception (IMAP)** : hôte, port (993 par défaut), chiffrement (SSL / TLS / aucun)
- **Envoi (SMTP)** : hôte, port (465 par défaut), chiffrement
- **Identifiants** : nom d'utilisateur + mot de passe (chiffré côté serveur, jamais réaffiché — un indicateur signale qu'un mot de passe est déjà enregistré), et chemin du dossier *Envoyés*
- **Toggle `enabled`** : active la messagerie
- **Test de connexion** intégré (vérifie l'accès IMAP et compte les dossiers)

View File

@@ -1,6 +1,6 @@
# Intégrations
Lesstime s'intègre avec **3 outils externes** pour fluidifier le workflow dev.
Lesstime s'intègre avec **4 outils externes** pour fluidifier le workflow dev.
## 🌳 Gitea
@@ -64,3 +64,29 @@ Sur une tâche avec **scheduled start + end** :
- **Pas de retour Zimbra → Lesstime** : si tu modifies l'événement dans Zimbra, Lesstime ne le voit pas
- **Récurrences** : les patterns RRULE basiques sont supportés (daily, weekly avec jours, monthly)
## 📧 Messagerie (Mail OVH)
Boîte mail partagée OVH (IMAP) lue directement dans Lesstime.
### Configuration
1. **Admin → Mail** :
- Réception **IMAP** (hôte, port, chiffrement) et envoi **SMTP** (hôte, port, chiffrement)
- Identifiants (mot de passe chiffré côté serveur) + dossier *Envoyés*
- **Test de connexion** intégré
2. Active la config (toggle `enabled`)
### Utilisation
- La section **Messagerie** (barre latérale) affiche dossiers, messages et lecteur
- **Synchronisation IMAP à la demande** via le bouton *Rafraîchir* (traitée en asynchrone par Messenger)
- Depuis un mail : **créer une tâche** pré-remplie ou **lier à une tâche** existante
- Badge de non-lus dans la barre latérale, rafraîchi automatiquement (toutes les 30 s)
> 📖 Le guide complet de la messagerie est dans la section *Messagerie*.
### Limites
- **Lecture seule** : pas de rédaction / réponse / suppression de mail depuis l'interface
- Réservée aux rôles **admin** et **user** (pas les clients)

View File

@@ -0,0 +1,40 @@
# Messagerie
Lesstime intègre une **boîte mail partagée** (OVH, protocole IMAP) directement dans l'application. Tu lis les mails de l'équipe et tu les transformes en tâches sans quitter Lesstime.
> 📥 La messagerie est accessible depuis l'entrée **Messagerie** de la barre latérale (icône enveloppe). Un **badge** y affiche le nombre de mails non lus, toutes boîtes confondues.
> 🛡️ Réservée aux rôles **ROLE_ADMIN** et **ROLE_USER**. Les utilisateurs *client* sont redirigés vers leur portail.
## L'interface
L'écran est organisé en **3 colonnes** :
1. **Dossiers** — l'arborescence de la boîte (INBOX, Envoyés, sous-dossiers…), avec le compteur de non-lus par dossier. INBOX est sélectionné par défaut.
2. **Messages** — la liste du dossier sélectionné (expéditeur, objet, date). Les mails non lus sont mis en avant. Un bouton **Charger plus** récupère les messages suivants (pagination).
3. **Lecteur** — le mail sélectionné : en-tête (expéditeur, destinataires, date), corps du message et **pièces jointes**.
## Lire un message
- Clique sur un message dans la liste : son détail s'affiche et il est **automatiquement marqué comme lu**.
- Tu peux le repasser **non lu** ou l'**étoiler** (flag) pour le retrouver plus tard.
- Les **pièces jointes** sont listées dans le lecteur : clique pour les télécharger, les images peuvent être prévisualisées.
## Synchronisation
- Le bouton **Rafraîchir** (en haut de l'écran) déclenche une **synchronisation IMAP à la demande** : Lesstime va chercher les nouveaux mails sur le serveur. Le traitement est asynchrone, la liste se met à jour quelques secondes après.
- En arrière-plan, le **compteur de non-lus** de la barre latérale se rafraîchit automatiquement (toutes les 30 s).
## Transformer un mail en action
Depuis le lecteur, deux boutons relient un mail au suivi de projet :
- **Créer une tâche** — ouvre une tâche pré-remplie à partir du mail (objet, contenu). Tu choisis le projet et les métadonnées, le mail reste lié à la tâche.
- **Lier à une tâche** — rattache le mail à une tâche **existante**.
> 💡 C'est le pont entre la boîte mail de l'équipe et le kanban : une demande reçue par mail devient une tâche traçable en deux clics.
## Limites
- **Lecture seule** : l'interface ne permet pas (encore) de **rédiger, répondre ou transférer** un mail, ni de supprimer un message.
- La configuration du serveur (IMAP/SMTP, identifiants) se fait dans **Admin → Mail** — voir la section *Administration*.

View File

@@ -24,6 +24,7 @@ const META: Record<string, { title: string, icon: string, accent: string, roles:
'07-admin': { title: 'Administration', icon: 'mdi:shield-crown-outline', accent: 'from-rose-500 to-pink-600', roles: ['admin'] },
'08-integrations': { title: 'Intégrations', icon: 'mdi:puzzle-outline', accent: 'from-blue-500 to-indigo-500', roles: ['admin', 'user'] },
'09-mcp-api': { title: 'Token MCP & API', icon: 'mdi:robot-outline', accent: 'from-slate-700 to-slate-900', roles: ['admin', 'user'] },
'10-messaging': { title: 'Messagerie', icon: 'mdi:email-outline', accent: 'from-teal-500 to-cyan-600', roles: ['admin', 'user'] },
}
const sections = computed<Section[]>(() => {

View File

@@ -15,6 +15,13 @@ JWT_COOKIE_SAMESITE=lax
JWT_TOKEN_TTL=86400
JWT_COOKIE_TTL=86400
# Mail (intégration IMAP/SMTP)
# Clé hex 32 bytes chiffrant les credentials mail stockés en base.
# Générer : php -r "echo bin2hex(random_bytes(32));" — doit rester STABLE.
ENCRYPTION_KEY=change-me
# Store de verrous Symfony pour la sync mail (anti-chevauchement du cron).
LOCK_DSN=flock
# CORS
CORS_ALLOW_ORIGIN='^https?://project\.malio-dev\.fr$'