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-lib | |
| 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
git clone <url-du-repo> notesfrais
cd notesfrais
2.2 Créer le fichier .env
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
# 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 :
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
- Dans Coolify, créer un nouveau service de type Docker Compose.
- Pointer sur le dépôt Git (branche
main). - Dans Variables d'environnement, renseigner les mêmes clés que dans
.env. - Dans Domaines, configurer
frais.domench.fr→ port80(Traefik gère le TLS). - Lancer le build ; Coolify s'occupe du certificat Let's Encrypt.
Astuce : le
docker-compose.ymlexpose 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
- Aller sur portal.azure.com → Microsoft Entra ID → Inscriptions d'applications → Nouvelle inscription.
- Nom :
NotesFrais— Type de compte : Locataire unique. - URI de redirection : aucune (flux client_credentials).
- Créer.
5.2 Créer un secret client
- Dans l'app → Certificats et secrets → Nouveau secret client.
- Durée recommandée : 24 mois.
- Copier la valeur immédiatement (elle n'est visible qu'une fois).
5.3 Accorder les permissions API
- Autorisations API → Ajouter une autorisation → Microsoft Graph → Autorisations d'application.
- Ajouter :
Files.ReadWrite.AllSites.ReadWrite.All
- Cliquer Accorder le consentement administrateur (bouton vert) — indispensable pour les permissions d'application.
5.4 Obtenir le Site ID SharePoint
Dans Graph Explorer (connecté avec un compte admin) :
GET https://graph.microsoft.com/v1.0/sites/<tenant>.sharepoint.com:/sites/<nom-du-site>
Remplacer <tenant> par le nom du tenant (ex. 1dotech) et <nom-du-site> 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/<site-id>/drive/root/children
Trouver le fichier Excel dans la liste et copier son champ id.
Alternative : naviguer dans l'arborescence avec
GET /sites/<site-id>/drive/root:/<chemin/vers/fichier.xlsx>
6. Mise à jour
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
# 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
# Copie locale du volume uploads
docker cp $(docker compose ps -q backend):/app/uploads ./uploads_backup
Restauration de la base de données
# 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
docker cp ./uploads_backup/. $(docker compose ps -q backend):/app/uploads/
Script de sauvegarde automatique (cron)
Créer /etc/cron.daily/notesfrais-backup :
#!/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"
chmod +x /etc/cron.daily/notesfrais-backup
8. Logs et débogage
# 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