324 lines
9.5 KiB
Markdown
324 lines
9.5 KiB
Markdown
# 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 <url-du-repo> 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/<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
|
|
|
|
```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
|
|
```
|