# NotesFrais PWA de gestion des notes de frais — capture photo, OCR, génération PDF, envoi par e-mail et export SharePoint. ## Stack technique | Couche | Techno | |--------|--------| | Frontend | React 18 + Vite + TailwindCSS + TanStack Query v5 | | Backend | Node.js 20 + Express + TypeScript | | Base de données | PostgreSQL 16 | | Stockage fichiers | Volume Docker (`/app/uploads`) | | Authentification | JWT (access 15 min + refresh 30 j) | | PDF | pdf-lib | | E-mail | Nodemailer (SMTP par société) | | SharePoint | Microsoft Graph API (client_credentials) | | PWA / offline | vite-plugin-pwa + Workbox + IndexedDB queue | | Déploiement | Docker multi-stage + Coolify + Traefik (HTTPS) | --- ## 1. Pré-requis - Docker ≥ 24 et Docker Compose v2 - Un domaine pointant sur le VPS (ex. `frais.domench.fr`) - Coolify installé sur le VPS (gère le routage HTTPS via Traefik) - Un **App Registration Azure / Entra ID** pour Microsoft Graph (voir §5) --- ## 2. Installation ### 2.1 Cloner le dépôt ```bash git clone notesfrais cd notesfrais ``` ### 2.2 Créer le fichier `.env` ```bash cp .env.example .env ``` Éditer `.env` et remplir toutes les valeurs `CHANGE_ME` : | Variable | Description | Commande de génération | |----------|-------------|------------------------| | `DOMAIN` | Domaine public (sans `https://`) | — | | `DB_PASSWORD` | Mot de passe PostgreSQL | `openssl rand -hex 32` | | `JWT_SECRET` | Secret de signature JWT | `openssl rand -hex 64` | | `APP_SECRET` | Clé AES-256 pour chiffrement BDD | `openssl rand -hex 32` | | `GREG_EMAIL` | E-mail du compte Greg (init uniquement) | — | | `GREG_PASSWORD` | Mot de passe du compte Greg | — | | `GAEL_EMAIL` | E-mail du compte Gaël (init uniquement) | — | | `GAEL_PASSWORD` | Mot de passe du compte Gaël | — | ### 2.3 Premier démarrage ```bash # Build et démarrage des conteneurs docker compose up -d --build # Vérifier que tout est sain docker compose ps docker compose logs backend --tail=50 ``` Le script `docker-entrypoint.sh` attend automatiquement que PostgreSQL soit prêt, puis exécute la migration. ### 2.4 Créer les utilisateurs initiaux Cette commande n'est à exécuter **qu'une seule fois**, juste après le premier démarrage : ```bash docker compose exec backend node dist/scripts/initUsers.js ``` Une fois les comptes créés, vous pouvez supprimer les variables `GREG_*` et `GAEL_*` du `.env`. --- ## 3. Déploiement avec Coolify 1. Dans Coolify, créer un nouveau service de type **Docker Compose**. 2. Pointer sur le dépôt Git (branche `main`). 3. Dans **Variables d'environnement**, renseigner les mêmes clés que dans `.env`. 4. Dans **Domaines**, configurer `frais.domench.fr` → port `80` (Traefik gère le TLS). 5. Lancer le build ; Coolify s'occupe du certificat Let's Encrypt. > **Astuce** : le `docker-compose.yml` expose uniquement le port 80 du frontend. > Traefik route le trafic HTTPS → HTTP interne. Le backend n'est pas exposé directement. --- ## 4. Paramétrage in-app Une fois connecté, aller dans **Réglages** : ### 4.1 Sociétés Ajouter chaque société avec son nom et son adresse e-mail de contact. ### 4.2 Configuration e-mail (par société) Renseigner les paramètres SMTP pour chaque société (hôte, port, TLS, utilisateur, mot de passe). Le mot de passe SMTP est chiffré en AES-256-GCM avant d'être stocké en base. ### 4.3 Microsoft Graph / SharePoint Renseigner une seule fois pour **toutes les sociétés** : | Champ | Description | |-------|-------------| | Tenant ID | ID du tenant Azure (voir §5) | | Client ID | ID de l'App Registration | | Client Secret | Secret de l'App Registration | | Site ID | ID du site SharePoint (voir §5.4) | | Item ID | ID du fichier Excel (voir §5.5) | | Nom de feuille | Nom de l'onglet Excel (ex. `App`) | --- ## 5. Configuration Microsoft Graph (Azure / Entra ID) ### 5.1 Créer l'App Registration 1. Aller sur [portal.azure.com](https://portal.azure.com) → **Microsoft Entra ID** → **Inscriptions d'applications** → **Nouvelle inscription**. 2. Nom : `NotesFrais` — Type de compte : **Locataire unique**. 3. URI de redirection : aucune (flux client_credentials). 4. **Créer**. ### 5.2 Créer un secret client 1. Dans l'app → **Certificats et secrets** → **Nouveau secret client**. 2. Durée recommandée : 24 mois. 3. **Copier la valeur immédiatement** (elle n'est visible qu'une fois). ### 5.3 Accorder les permissions API 1. **Autorisations API** → **Ajouter une autorisation** → **Microsoft Graph** → **Autorisations d'application**. 2. Ajouter : - `Files.ReadWrite.All` - `Sites.ReadWrite.All` 3. Cliquer **Accorder le consentement administrateur** (bouton vert) — indispensable pour les permissions d'application. ### 5.4 Obtenir le Site ID SharePoint Dans [Graph Explorer](https://developer.microsoft.com/en-us/graph/graph-explorer) (connecté avec un compte admin) : ``` GET https://graph.microsoft.com/v1.0/sites/.sharepoint.com:/sites/ ``` Remplacer `` par le nom du tenant (ex. `1dotech`) et `` par le chemin du site. Récupérer la valeur du champ `id` dans la réponse (format : `host,guid1,guid2`). ### 5.5 Obtenir l'Item ID du fichier Excel ``` GET https://graph.microsoft.com/v1.0/sites//drive/root/children ``` Trouver le fichier Excel dans la liste et copier son champ `id`. > **Alternative** : naviguer dans l'arborescence avec > `GET /sites//drive/root:/` --- ## 6. Mise à jour ```bash git pull docker compose up -d --build ``` Le `docker-entrypoint.sh` ré-applique la migration à chaque démarrage (toutes les instructions SQL utilisent `IF NOT EXISTS` / `ON CONFLICT DO NOTHING` — elles sont idempotentes). --- ## 7. Sauvegarde et restauration ### Sauvegarde de la base de données ```bash # Dump compressé horodaté docker compose exec db pg_dump -U notesfrais notesfrais \ | gzip > "backup_notesfrais_$(date +%Y%m%d_%H%M%S).sql.gz" ``` ### Sauvegarde des fichiers uploadés ```bash # Copie locale du volume uploads docker cp $(docker compose ps -q backend):/app/uploads ./uploads_backup ``` ### Restauration de la base de données ```bash # Arrêter le backend le temps de la restauration docker compose stop backend # Restaurer gunzip -c backup_notesfrais_YYYYMMDD_HHMMSS.sql.gz \ | docker compose exec -T db psql -U notesfrais notesfrais # Redémarrer docker compose start backend ``` ### Restauration des fichiers ```bash docker cp ./uploads_backup/. $(docker compose ps -q backend):/app/uploads/ ``` ### Script de sauvegarde automatique (cron) Créer `/etc/cron.daily/notesfrais-backup` : ```bash #!/bin/bash set -e BACKUP_DIR=/var/backups/notesfrais mkdir -p "$BACKUP_DIR" # Garder 30 jours find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete # Dump BDD cd /chemin/vers/notesfrais docker compose exec -T db pg_dump -U notesfrais notesfrais \ | gzip > "$BACKUP_DIR/db_$(date +%Y%m%d_%H%M%S).sql.gz" ``` ```bash chmod +x /etc/cron.daily/notesfrais-backup ``` --- ## 8. Logs et débogage ```bash # Tous les services docker compose logs -f # Backend uniquement docker compose logs -f backend # Vérifier l'état des conteneurs docker compose ps # Accéder au shell backend docker compose exec backend sh # Console PostgreSQL docker compose exec db psql -U notesfrais notesfrais ``` --- ## 9. Variables d'environnement — référence complète ### Fichier `.env` (racine, pour docker-compose) | Variable | Obligatoire | Défaut | Description | |----------|-------------|--------|-------------| | `DOMAIN` | Oui | `frais.domench.fr` | Domaine public | | `DB_PASSWORD` | Oui | — | Mot de passe PostgreSQL | | `JWT_SECRET` | Oui | — | Secret JWT (≥ 64 octets hex) | | `APP_SECRET` | Oui | — | Clé AES-256 (32 octets hex) | | `GREG_EMAIL` | Init seult. | — | E-mail compte Greg | | `GREG_PASSWORD` | Init seult. | — | Mot de passe compte Greg | | `GAEL_EMAIL` | Init seult. | — | E-mail compte Gaël | | `GAEL_PASSWORD` | Init seult. | — | Mot de passe compte Gaël | ### Variables injectées par docker-compose (non à configurer dans `.env`) | Variable | Valeur fixée dans docker-compose.yml | |----------|--------------------------------------| | `NODE_ENV` | `production` | | `PORT` | `3001` | | `DATABASE_URL` | construit depuis `DB_PASSWORD` | | `FRONTEND_URL` | construit depuis `DOMAIN` | | `UPLOADS_DIR` | `/app/uploads` | --- ## 10. Structure du projet ``` notesfrais/ ├── backend/ │ ├── src/ │ │ ├── index.ts # Point d'entrée Express │ │ ├── config.ts # Variables d'env │ │ ├── db.ts # Pool PostgreSQL │ │ ├── middleware/ # Auth JWT, error handler │ │ ├── routes/ # auth, invoices, companies, settings, guests │ │ ├── services/ # pdf, email, sharepoint │ │ ├── migrations/ # 001_init.sql │ │ └── scripts/ # migrate.ts, initUsers.ts │ ├── Dockerfile │ ├── docker-entrypoint.sh │ └── package.json ├── frontend/ │ ├── src/ │ │ ├── pages/ # Login, NewInvoice, MyInvoices, Settings… │ │ ├── components/ # Layout, modaux… │ │ ├── hooks/ # useOfflineQueue, useOnline… │ │ ├── utils/ # offlineQueue (IndexedDB), api… │ │ └── types/ # index.ts │ ├── Dockerfile │ ├── nginx.conf │ └── package.json ├── docker-compose.yml ├── .env.example └── README.md ```